Make link last commit massages in repository home page and commit tables (#8006)
* Make link last commit massages in repository home page and commit tables * Use RenderCommitMessageLink instead surround with a * deleted __debug_bin file * Exclude email to link from latest commit title * Exclude email processor from commit table Co-Authored-By: mrsdizzie <info@mrsdizzie.com> * Add class parameter to a html element creator functions. Make links underline dashed that are not commit * fix tests * Show dashed underline when also not hovered
This commit is contained in:
		
							parent
							
								
									7eacdcf39a
								
							
						
					
					
						commit
						7e17424c7e
					
				
					 7 changed files with 154 additions and 38 deletions
				
			
		|  | @ -211,6 +211,40 @@ func RenderCommitMessage( | ||||||
| 	return ctx.postProcess(rawHTML) | 	return ctx.postProcess(rawHTML) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var commitMessageSubjectProcessors = []processor{ | ||||||
|  | 	fullIssuePatternProcessor, | ||||||
|  | 	fullSha1PatternProcessor, | ||||||
|  | 	linkProcessor, | ||||||
|  | 	mentionProcessor, | ||||||
|  | 	issueIndexPatternProcessor, | ||||||
|  | 	crossReferenceIssueIndexPatternProcessor, | ||||||
|  | 	sha1CurrentPatternProcessor, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RenderCommitMessageSubject will use the same logic as PostProcess and
 | ||||||
|  | // RenderCommitMessage, but will disable the shortLinkProcessor and
 | ||||||
|  | // emailAddressProcessor, will add a defaultLinkProcessor if defaultLink is set,
 | ||||||
|  | // which changes every text node into a link to the passed default link.
 | ||||||
|  | func RenderCommitMessageSubject( | ||||||
|  | 	rawHTML []byte, | ||||||
|  | 	urlPrefix, defaultLink string, | ||||||
|  | 	metas map[string]string, | ||||||
|  | ) ([]byte, error) { | ||||||
|  | 	ctx := &postProcessCtx{ | ||||||
|  | 		metas:     metas, | ||||||
|  | 		urlPrefix: urlPrefix, | ||||||
|  | 		procs:     commitMessageSubjectProcessors, | ||||||
|  | 	} | ||||||
|  | 	if defaultLink != "" { | ||||||
|  | 		// we don't have to fear data races, because being
 | ||||||
|  | 		// commitMessageSubjectProcessors of fixed len and cap, every time we
 | ||||||
|  | 		// append something to it the slice is realloc+copied, so append always
 | ||||||
|  | 		// generates the slice ex-novo.
 | ||||||
|  | 		ctx.procs = append(ctx.procs, genDefaultLinkProcessor(defaultLink)) | ||||||
|  | 	} | ||||||
|  | 	return ctx.postProcess(rawHTML) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // RenderDescriptionHTML will use similar logic as PostProcess, but will
 | // RenderDescriptionHTML will use similar logic as PostProcess, but will
 | ||||||
| // use a single special linkProcessor.
 | // use a single special linkProcessor.
 | ||||||
| func RenderDescriptionHTML( | func RenderDescriptionHTML( | ||||||
|  | @ -296,12 +330,17 @@ func (ctx *postProcessCtx) textNode(node *html.Node) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func createLink(href, content string) *html.Node { | func createLink(href, content, class string) *html.Node { | ||||||
| 	a := &html.Node{ | 	a := &html.Node{ | ||||||
| 		Type: html.ElementNode, | 		Type: html.ElementNode, | ||||||
| 		Data: atom.A.String(), | 		Data: atom.A.String(), | ||||||
| 		Attr: []html.Attribute{{Key: "href", Val: href}}, | 		Attr: []html.Attribute{{Key: "href", Val: href}}, | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if class != "" { | ||||||
|  | 		a.Attr = append(a.Attr, html.Attribute{Key: "class", Val: class}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	text := &html.Node{ | 	text := &html.Node{ | ||||||
| 		Type: html.TextNode, | 		Type: html.TextNode, | ||||||
| 		Data: content, | 		Data: content, | ||||||
|  | @ -311,12 +350,17 @@ func createLink(href, content string) *html.Node { | ||||||
| 	return a | 	return a | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func createCodeLink(href, content string) *html.Node { | func createCodeLink(href, content, class string) *html.Node { | ||||||
| 	a := &html.Node{ | 	a := &html.Node{ | ||||||
| 		Type: html.ElementNode, | 		Type: html.ElementNode, | ||||||
| 		Data: atom.A.String(), | 		Data: atom.A.String(), | ||||||
| 		Attr: []html.Attribute{{Key: "href", Val: href}}, | 		Attr: []html.Attribute{{Key: "href", Val: href}}, | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if class != "" { | ||||||
|  | 		a.Attr = append(a.Attr, html.Attribute{Key: "class", Val: class}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	text := &html.Node{ | 	text := &html.Node{ | ||||||
| 		Type: html.TextNode, | 		Type: html.TextNode, | ||||||
| 		Data: content, | 		Data: content, | ||||||
|  | @ -364,7 +408,7 @@ func mentionProcessor(_ *postProcessCtx, node *html.Node) { | ||||||
| 	} | 	} | ||||||
| 	// Replace the mention with a link to the specified user.
 | 	// Replace the mention with a link to the specified user.
 | ||||||
| 	mention := node.Data[m[2]:m[3]] | 	mention := node.Data[m[2]:m[3]] | ||||||
| 	replaceContent(node, m[2], m[3], createLink(util.URLJoin(setting.AppURL, mention[1:]), mention)) | 	replaceContent(node, m[2], m[3], createLink(util.URLJoin(setting.AppURL, mention[1:]), mention, "mention")) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func shortLinkProcessor(ctx *postProcessCtx, node *html.Node) { | func shortLinkProcessor(ctx *postProcessCtx, node *html.Node) { | ||||||
|  | @ -541,11 +585,11 @@ func fullIssuePatternProcessor(ctx *postProcessCtx, node *html.Node) { | ||||||
| 	if matchOrg == ctx.metas["user"] && matchRepo == ctx.metas["repo"] { | 	if matchOrg == ctx.metas["user"] && matchRepo == ctx.metas["repo"] { | ||||||
| 		// TODO if m[4]:m[5] is not nil, then link is to a comment,
 | 		// TODO if m[4]:m[5] is not nil, then link is to a comment,
 | ||||||
| 		// and we should indicate that in the text somehow
 | 		// and we should indicate that in the text somehow
 | ||||||
| 		replaceContent(node, m[0], m[1], createLink(link, id)) | 		replaceContent(node, m[0], m[1], createLink(link, id, "issue")) | ||||||
| 
 | 
 | ||||||
| 	} else { | 	} else { | ||||||
| 		orgRepoID := matchOrg + "/" + matchRepo + id | 		orgRepoID := matchOrg + "/" + matchRepo + id | ||||||
| 		replaceContent(node, m[0], m[1], createLink(link, orgRepoID)) | 		replaceContent(node, m[0], m[1], createLink(link, orgRepoID, "issue")) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -573,9 +617,9 @@ func issueIndexPatternProcessor(ctx *postProcessCtx, node *html.Node) { | ||||||
| 		} else { | 		} else { | ||||||
| 			ctx.metas["index"] = id[1:] | 			ctx.metas["index"] = id[1:] | ||||||
| 		} | 		} | ||||||
| 		link = createLink(com.Expand(ctx.metas["format"], ctx.metas), id) | 		link = createLink(com.Expand(ctx.metas["format"], ctx.metas), id, "issue") | ||||||
| 	} else { | 	} else { | ||||||
| 		link = createLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "issues", id[1:]), id) | 		link = createLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "issues", id[1:]), id, "issue") | ||||||
| 	} | 	} | ||||||
| 	replaceContent(node, match[2], match[3], link) | 	replaceContent(node, match[2], match[3], link) | ||||||
| } | } | ||||||
|  | @ -591,7 +635,7 @@ func crossReferenceIssueIndexPatternProcessor(ctx *postProcessCtx, node *html.No | ||||||
| 	repo, issue := parts[0], parts[1] | 	repo, issue := parts[0], parts[1] | ||||||
| 
 | 
 | ||||||
| 	replaceContent(node, m[2], m[3], | 	replaceContent(node, m[2], m[3], | ||||||
| 		createLink(util.URLJoin(setting.AppURL, repo, "issues", issue), ref)) | 		createLink(util.URLJoin(setting.AppURL, repo, "issues", issue), ref, issue)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // fullSha1PatternProcessor renders SHA containing URLs
 | // fullSha1PatternProcessor renders SHA containing URLs
 | ||||||
|  | @ -642,7 +686,7 @@ func fullSha1PatternProcessor(ctx *postProcessCtx, node *html.Node) { | ||||||
| 		text += " (" + hash + ")" | 		text += " (" + hash + ")" | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	replaceContent(node, start, end, createCodeLink(urlFull, text)) | 	replaceContent(node, start, end, createCodeLink(urlFull, text, "commit")) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // sha1CurrentPatternProcessor renders SHA1 strings to corresponding links that
 | // sha1CurrentPatternProcessor renders SHA1 strings to corresponding links that
 | ||||||
|  | @ -672,7 +716,7 @@ func sha1CurrentPatternProcessor(ctx *postProcessCtx, node *html.Node) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	replaceContent(node, m[2], m[3], | 	replaceContent(node, m[2], m[3], | ||||||
| 		createCodeLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "commit", hash), base.ShortSha(hash))) | 		createCodeLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "commit", hash), base.ShortSha(hash), "commit")) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // emailAddressProcessor replaces raw email addresses with a mailto: link.
 | // emailAddressProcessor replaces raw email addresses with a mailto: link.
 | ||||||
|  | @ -682,7 +726,7 @@ func emailAddressProcessor(ctx *postProcessCtx, node *html.Node) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	mail := node.Data[m[2]:m[3]] | 	mail := node.Data[m[2]:m[3]] | ||||||
| 	replaceContent(node, m[2], m[3], createLink("mailto:"+mail, mail)) | 	replaceContent(node, m[2], m[3], createLink("mailto:"+mail, mail, "mailto")) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // linkProcessor creates links for any HTTP or HTTPS URL not captured by
 | // linkProcessor creates links for any HTTP or HTTPS URL not captured by
 | ||||||
|  | @ -693,7 +737,7 @@ func linkProcessor(ctx *postProcessCtx, node *html.Node) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	uri := node.Data[m[0]:m[1]] | 	uri := node.Data[m[0]:m[1]] | ||||||
| 	replaceContent(node, m[0], m[1], createLink(uri, uri)) | 	replaceContent(node, m[0], m[1], createLink(uri, uri, "link")) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func genDefaultLinkProcessor(defaultLink string) processor { | func genDefaultLinkProcessor(defaultLink string) processor { | ||||||
|  | @ -707,7 +751,10 @@ func genDefaultLinkProcessor(defaultLink string) processor { | ||||||
| 		node.Type = html.ElementNode | 		node.Type = html.ElementNode | ||||||
| 		node.Data = "a" | 		node.Data = "a" | ||||||
| 		node.DataAtom = atom.A | 		node.DataAtom = atom.A | ||||||
| 		node.Attr = []html.Attribute{{Key: "href", Val: defaultLink}} | 		node.Attr = []html.Attribute{ | ||||||
|  | 			{Key: "href", Val: defaultLink}, | ||||||
|  | 			{Key: "class", Val: "default-link"}, | ||||||
|  | 		} | ||||||
| 		node.FirstChild, node.LastChild = ch, ch | 		node.FirstChild, node.LastChild = ch, ch | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -20,18 +20,22 @@ const Repo = "gogits/gogs" | ||||||
| const AppSubURL = AppURL + Repo + "/" | const AppSubURL = AppURL + Repo + "/" | ||||||
| 
 | 
 | ||||||
| // alphanumLink an HTML link to an alphanumeric-style issue
 | // alphanumLink an HTML link to an alphanumeric-style issue
 | ||||||
| func alphanumIssueLink(baseURL string, name string) string { | func alphanumIssueLink(baseURL, class, name string) string { | ||||||
| 	return link(util.URLJoin(baseURL, name), name) | 	return link(util.URLJoin(baseURL, name), class, name) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // numericLink an HTML to a numeric-style issue
 | // numericLink an HTML to a numeric-style issue
 | ||||||
| func numericIssueLink(baseURL string, index int) string { | func numericIssueLink(baseURL, class string, index int) string { | ||||||
| 	return link(util.URLJoin(baseURL, strconv.Itoa(index)), fmt.Sprintf("#%d", index)) | 	return link(util.URLJoin(baseURL, strconv.Itoa(index)), class, fmt.Sprintf("#%d", index)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // link an HTML link
 | // link an HTML link
 | ||||||
| func link(href, contents string) string { | func link(href, class, contents string) string { | ||||||
| 	return fmt.Sprintf("<a href=\"%s\">%s</a>", href, contents) | 	if class != "" { | ||||||
|  | 		class = " class=\"" + class + "\"" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return fmt.Sprintf("<a href=\"%s\"%s>%s</a>", href, class, contents) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var numericMetas = map[string]string{ | var numericMetas = map[string]string{ | ||||||
|  | @ -89,13 +93,13 @@ func TestRender_IssueIndexPattern2(t *testing.T) { | ||||||
| 	test := func(s, expectedFmt string, indices ...int) { | 	test := func(s, expectedFmt string, indices ...int) { | ||||||
| 		links := make([]interface{}, len(indices)) | 		links := make([]interface{}, len(indices)) | ||||||
| 		for i, index := range indices { | 		for i, index := range indices { | ||||||
| 			links[i] = numericIssueLink(util.URLJoin(setting.AppSubURL, "issues"), index) | 			links[i] = numericIssueLink(util.URLJoin(setting.AppSubURL, "issues"), "issue", index) | ||||||
| 		} | 		} | ||||||
| 		expectedNil := fmt.Sprintf(expectedFmt, links...) | 		expectedNil := fmt.Sprintf(expectedFmt, links...) | ||||||
| 		testRenderIssueIndexPattern(t, s, expectedNil, &postProcessCtx{metas: localMetas}) | 		testRenderIssueIndexPattern(t, s, expectedNil, &postProcessCtx{metas: localMetas}) | ||||||
| 
 | 
 | ||||||
| 		for i, index := range indices { | 		for i, index := range indices { | ||||||
| 			links[i] = numericIssueLink("https://someurl.com/someUser/someRepo/", index) | 			links[i] = numericIssueLink("https://someurl.com/someUser/someRepo/", "issue", index) | ||||||
| 		} | 		} | ||||||
| 		expectedNum := fmt.Sprintf(expectedFmt, links...) | 		expectedNum := fmt.Sprintf(expectedFmt, links...) | ||||||
| 		testRenderIssueIndexPattern(t, s, expectedNum, &postProcessCtx{metas: numericMetas}) | 		testRenderIssueIndexPattern(t, s, expectedNum, &postProcessCtx{metas: numericMetas}) | ||||||
|  | @ -158,7 +162,7 @@ func TestRender_IssueIndexPattern4(t *testing.T) { | ||||||
| 	test := func(s, expectedFmt string, names ...string) { | 	test := func(s, expectedFmt string, names ...string) { | ||||||
| 		links := make([]interface{}, len(names)) | 		links := make([]interface{}, len(names)) | ||||||
| 		for i, name := range names { | 		for i, name := range names { | ||||||
| 			links[i] = alphanumIssueLink("https://someurl.com/someUser/someRepo/", name) | 			links[i] = alphanumIssueLink("https://someurl.com/someUser/someRepo/", "issue", name) | ||||||
| 		} | 		} | ||||||
| 		expected := fmt.Sprintf(expectedFmt, links...) | 		expected := fmt.Sprintf(expectedFmt, links...) | ||||||
| 		testRenderIssueIndexPattern(t, s, expected, &postProcessCtx{metas: alphanumericMetas}) | 		testRenderIssueIndexPattern(t, s, expected, &postProcessCtx{metas: alphanumericMetas}) | ||||||
|  | @ -197,17 +201,17 @@ func TestRender_AutoLink(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	// render valid issue URLs
 | 	// render valid issue URLs
 | ||||||
| 	test(util.URLJoin(setting.AppSubURL, "issues", "3333"), | 	test(util.URLJoin(setting.AppSubURL, "issues", "3333"), | ||||||
| 		numericIssueLink(util.URLJoin(setting.AppSubURL, "issues"), 3333)) | 		numericIssueLink(util.URLJoin(setting.AppSubURL, "issues"), "issue", 3333)) | ||||||
| 
 | 
 | ||||||
| 	// render valid commit URLs
 | 	// render valid commit URLs
 | ||||||
| 	tmp := util.URLJoin(AppSubURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") | 	tmp := util.URLJoin(AppSubURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") | ||||||
| 	test(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">d8a994ef24</code></a>") | 	test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24</code></a>") | ||||||
| 	tmp += "#diff-2" | 	tmp += "#diff-2" | ||||||
| 	test(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>") | 	test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>") | ||||||
| 
 | 
 | ||||||
| 	// render other commit URLs
 | 	// render other commit URLs
 | ||||||
| 	tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2" | 	tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2" | ||||||
| 	test(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>") | 	test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestRender_FullIssueURLs(t *testing.T) { | func TestRender_FullIssueURLs(t *testing.T) { | ||||||
|  | @ -228,11 +232,11 @@ func TestRender_FullIssueURLs(t *testing.T) { | ||||||
| 	test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6", | 	test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6", | ||||||
| 		"Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6") | 		"Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6") | ||||||
| 	test("Look here http://localhost:3000/person/repo/issues/4", | 	test("Look here http://localhost:3000/person/repo/issues/4", | ||||||
| 		`Look here <a href="http://localhost:3000/person/repo/issues/4">person/repo#4</a>`) | 		`Look here <a href="http://localhost:3000/person/repo/issues/4" class="issue">person/repo#4</a>`) | ||||||
| 	test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", | 	test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", | ||||||
| 		`<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234">person/repo#4</a>`) | 		`<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="issue">person/repo#4</a>`) | ||||||
| 	test("http://localhost:3000/gogits/gogs/issues/4", | 	test("http://localhost:3000/gogits/gogs/issues/4", | ||||||
| 		`<a href="http://localhost:3000/gogits/gogs/issues/4">#4</a>`) | 		`<a href="http://localhost:3000/gogits/gogs/issues/4" class="issue">#4</a>`) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestRegExp_issueNumericPattern(t *testing.T) { | func TestRegExp_issueNumericPattern(t *testing.T) { | ||||||
|  |  | ||||||
|  | @ -118,13 +118,14 @@ func NewFuncMap() []template.FuncMap { | ||||||
| 		"EscapePound": func(str string) string { | 		"EscapePound": func(str string) string { | ||||||
| 			return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str) | 			return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str) | ||||||
| 		}, | 		}, | ||||||
| 		"PathEscapeSegments":       util.PathEscapeSegments, | 		"PathEscapeSegments":             util.PathEscapeSegments, | ||||||
| 		"URLJoin":                  util.URLJoin, | 		"URLJoin":                        util.URLJoin, | ||||||
| 		"RenderCommitMessage":      RenderCommitMessage, | 		"RenderCommitMessage":            RenderCommitMessage, | ||||||
| 		"RenderCommitMessageLink":  RenderCommitMessageLink, | 		"RenderCommitMessageLink":        RenderCommitMessageLink, | ||||||
| 		"RenderCommitBody":         RenderCommitBody, | 		"RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject, | ||||||
| 		"RenderNote":               RenderNote, | 		"RenderCommitBody":               RenderCommitBody, | ||||||
| 		"IsMultilineCommitMessage": IsMultilineCommitMessage, | 		"RenderNote":                     RenderNote, | ||||||
|  | 		"IsMultilineCommitMessage":       IsMultilineCommitMessage, | ||||||
| 		"ThemeColorMetaTag": func() string { | 		"ThemeColorMetaTag": func() string { | ||||||
| 			return setting.UI.ThemeColorMetaTag | 			return setting.UI.ThemeColorMetaTag | ||||||
| 		}, | 		}, | ||||||
|  | @ -322,6 +323,24 @@ func RenderCommitMessageLink(msg, urlPrefix, urlDefault string, metas map[string | ||||||
| 	return template.HTML(msgLines[0]) | 	return template.HTML(msgLines[0]) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to
 | ||||||
|  | // the provided default url, handling for special links without email to links.
 | ||||||
|  | func RenderCommitMessageLinkSubject(msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { | ||||||
|  | 	cleanMsg := template.HTMLEscapeString(msg) | ||||||
|  | 	// we can safely assume that it will not return any error, since there
 | ||||||
|  | 	// shouldn't be any special HTML.
 | ||||||
|  | 	fullMessage, err := markup.RenderCommitMessageSubject([]byte(cleanMsg), urlPrefix, urlDefault, metas) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error("RenderCommitMessageSubject: %v", err) | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	msgLines := strings.Split(strings.TrimSpace(string(fullMessage)), "\n") | ||||||
|  | 	if len(msgLines) == 0 { | ||||||
|  | 		return template.HTML("") | ||||||
|  | 	} | ||||||
|  | 	return template.HTML(msgLines[0]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // RenderCommitBody extracts the body of a commit message without its title.
 | // RenderCommitBody extracts the body of a commit message without its title.
 | ||||||
| func RenderCommitBody(msg, urlPrefix string, metas map[string]string) template.HTML { | func RenderCommitBody(msg, urlPrefix string, metas map[string]string) template.HTML { | ||||||
| 	cleanMsg := template.HTMLEscapeString(msg) | 	cleanMsg := template.HTMLEscapeString(msg) | ||||||
|  |  | ||||||
|  | @ -466,6 +466,10 @@ footer .ui.left,footer .ui.right{line-height:40px} | ||||||
| } | } | ||||||
| .repository.file.list #repo-files-table thead th{padding-top:8px;padding-bottom:5px;font-weight:400} | .repository.file.list #repo-files-table thead th{padding-top:8px;padding-bottom:5px;font-weight:400} | ||||||
| .repository.file.list #repo-files-table thead .ui.avatar{margin-bottom:5px} | .repository.file.list #repo-files-table thead .ui.avatar{margin-bottom:5px} | ||||||
|  | .repository.file.list #repo-files-table thead .commit-summary a{text-decoration:underline;-webkit-text-decoration-style:dashed;text-decoration-style:dashed} | ||||||
|  | .repository.file.list #repo-files-table thead .commit-summary a:hover{-webkit-text-decoration-style:solid;text-decoration-style:solid} | ||||||
|  | .repository.file.list #repo-files-table thead .commit-summary a.default-link{text-decoration:none} | ||||||
|  | .repository.file.list #repo-files-table thead .commit-summary a.default-link:hover{text-decoration:underline;-webkit-text-decoration-style:solid;text-decoration-style:solid} | ||||||
| .repository.file.list #repo-files-table tbody .octicon{margin-left:3px;margin-right:5px;color:#777} | .repository.file.list #repo-files-table tbody .octicon{margin-left:3px;margin-right:5px;color:#777} | ||||||
| .repository.file.list #repo-files-table tbody .octicon.octicon-mail-reply{margin-right:10px} | .repository.file.list #repo-files-table tbody .octicon.octicon-mail-reply{margin-right:10px} | ||||||
| .repository.file.list #repo-files-table tbody .octicon.octicon-file-directory,.repository.file.list #repo-files-table tbody .octicon.octicon-file-submodule,.repository.file.list #repo-files-table tbody .octicon.octicon-file-symlink-directory{color:#1e70bf} | .repository.file.list #repo-files-table tbody .octicon.octicon-file-directory,.repository.file.list #repo-files-table tbody .octicon.octicon-file-submodule,.repository.file.list #repo-files-table tbody .octicon.octicon-file-symlink-directory{color:#1e70bf} | ||||||
|  | @ -829,6 +833,10 @@ footer .ui.left,footer .ui.right{line-height:40px} | ||||||
| .stats-table .table-cell.tiny{height:.5em} | .stats-table .table-cell.tiny{height:.5em} | ||||||
| tbody.commit-list{vertical-align:baseline} | tbody.commit-list{vertical-align:baseline} | ||||||
| .commit-list .message-wrapper{overflow:hidden;text-overflow:ellipsis;max-width:calc(100% - 50px);display:inline-block;vertical-align:middle} | .commit-list .message-wrapper{overflow:hidden;text-overflow:ellipsis;max-width:calc(100% - 50px);display:inline-block;vertical-align:middle} | ||||||
|  | .commit-list .commit-summary a{text-decoration:underline;-webkit-text-decoration-style:dashed;text-decoration-style:dashed} | ||||||
|  | .commit-list .commit-summary a:hover{-webkit-text-decoration-style:solid;text-decoration-style:solid} | ||||||
|  | .commit-list .commit-summary a.default-link{text-decoration:none} | ||||||
|  | .commit-list .commit-summary a.default-link:hover{text-decoration:underline;-webkit-text-decoration-style:solid;text-decoration-style:solid} | ||||||
| .commit-list .commit-status-link{display:inline-block;vertical-align:middle} | .commit-list .commit-status-link{display:inline-block;vertical-align:middle} | ||||||
| .commit-body{white-space:pre-wrap} | .commit-body{white-space:pre-wrap} | ||||||
| .git-notes.top{text-align:left} | .git-notes.top{text-align:left} | ||||||
|  |  | ||||||
|  | @ -277,6 +277,24 @@ | ||||||
|                 .ui.avatar { |                 .ui.avatar { | ||||||
|                     margin-bottom: 5px; |                     margin-bottom: 5px; | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|  |                 .commit-summary a { | ||||||
|  |                     text-decoration: underline; | ||||||
|  |                     text-decoration-style: dashed; | ||||||
|  | 
 | ||||||
|  |                     &:hover { | ||||||
|  |                         text-decoration-style: solid; | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     &.default-link { | ||||||
|  |                         text-decoration: none; | ||||||
|  | 
 | ||||||
|  |                         &:hover { | ||||||
|  |                             text-decoration: underline; | ||||||
|  |                             text-decoration-style: solid; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             tbody { |             tbody { | ||||||
|  | @ -2188,6 +2206,24 @@ tbody.commit-list { | ||||||
|     vertical-align: middle; |     vertical-align: middle; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .commit-list .commit-summary a { | ||||||
|  |     text-decoration: underline; | ||||||
|  |     text-decoration-style: dashed; | ||||||
|  | 
 | ||||||
|  |     &:hover { | ||||||
|  |         text-decoration-style: solid; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &.default-link { | ||||||
|  |         text-decoration: none; | ||||||
|  | 
 | ||||||
|  |         &:hover { | ||||||
|  |             text-decoration: underline; | ||||||
|  |             text-decoration-style: solid; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .commit-list .commit-status-link { | .commit-list .commit-status-link { | ||||||
|     display: inline-block; |     display: inline-block; | ||||||
|     vertical-align: middle; |     vertical-align: middle; | ||||||
|  |  | ||||||
|  | @ -71,7 +71,8 @@ | ||||||
| 						</td> | 						</td> | ||||||
| 						<td class="message"> | 						<td class="message"> | ||||||
| 							<span class="message-wrapper"> | 							<span class="message-wrapper"> | ||||||
| 								<span class="commit-summary has-emoji{{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessage .Message $.RepoLink $.Repository.ComposeMetas}}</span> | 								{{ $commitLink:= printf "%s/%s/%s/commit/%s" AppSubUrl $.Username $.Reponame .ID }} | ||||||
|  | 								<span class="commit-summary has-emoji{{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject .Message $.RepoLink $commitLink $.Repository.ComposeMetas}}</span> | ||||||
| 							</span> | 							</span> | ||||||
| 							{{if IsMultilineCommitMessage .Message}} | 							{{if IsMultilineCommitMessage .Message}} | ||||||
| 							<button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button> | 							<button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button> | ||||||
|  |  | ||||||
|  | @ -28,7 +28,8 @@ | ||||||
| 						{{end}} | 						{{end}} | ||||||
| 				</a> | 				</a> | ||||||
| 				{{template "repo/commit_status" .LatestCommitStatus}} | 				{{template "repo/commit_status" .LatestCommitStatus}} | ||||||
| 				<span class="grey has-emoji commit-summary" title="{{.LatestCommit.Summary}}">{{RenderCommitMessage .LatestCommit.Message $.RepoLink $.Repository.ComposeMetas}} | 				{{ $commitLink:= printf "%s/commit/%s" .RepoLink .LatestCommit.ID }} | ||||||
|  | 				<span class="grey has-emoji commit-summary" title="{{.LatestCommit.Summary}}">{{RenderCommitMessageLinkSubject .LatestCommit.Message $.RepoLink $commitLink $.Repository.ComposeMetas}} | ||||||
| 				{{if IsMultilineCommitMessage .LatestCommit.Message}} | 				{{if IsMultilineCommitMessage .LatestCommit.Message}} | ||||||
| 					<button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button> | 					<button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button> | ||||||
| 					<pre class="commit-body" style="display: none;">{{RenderCommitBody .LatestCommit.Message $.RepoLink $.Repository.ComposeMetas}}</pre> | 					<pre class="commit-body" style="display: none;">{{RenderCommitBody .LatestCommit.Message $.RepoLink $.Repository.ComposeMetas}}</pre> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue