Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make link last commit massages in repository home page and commit tables #8006

Merged
merged 11 commits into from
Sep 10, 2019
73 changes: 60 additions & 13 deletions modules/markup/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,40 @@ func RenderCommitMessage(
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
// use a single special linkProcessor.
func RenderDescriptionHTML(
Expand Down Expand Up @@ -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{
Type: html.ElementNode,
Data: atom.A.String(),
Attr: []html.Attribute{{Key: "href", Val: href}},
}

if class != "" {
a.Attr = append(a.Attr, html.Attribute{Key: "class", Val: class})
}

text := &html.Node{
Type: html.TextNode,
Data: content,
Expand All @@ -311,12 +350,17 @@ func createLink(href, content string) *html.Node {
return a
}

func createCodeLink(href, content string) *html.Node {
func createCodeLink(href, content, class string) *html.Node {
a := &html.Node{
Type: html.ElementNode,
Data: atom.A.String(),
Attr: []html.Attribute{{Key: "href", Val: href}},
}

if class != "" {
a.Attr = append(a.Attr, html.Attribute{Key: "class", Val: class})
}

text := &html.Node{
Type: html.TextNode,
Data: content,
Expand Down Expand Up @@ -364,7 +408,7 @@ func mentionProcessor(_ *postProcessCtx, node *html.Node) {
}
// Replace the mention with a link to the specified user.
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) {
Expand Down Expand Up @@ -541,11 +585,11 @@ func fullIssuePatternProcessor(ctx *postProcessCtx, node *html.Node) {
if matchOrg == ctx.metas["user"] && matchRepo == ctx.metas["repo"] {
// TODO if m[4]:m[5] is not nil, then link is to a comment,
// 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 {
orgRepoID := matchOrg + "/" + matchRepo + id
replaceContent(node, m[0], m[1], createLink(link, orgRepoID))
replaceContent(node, m[0], m[1], createLink(link, orgRepoID, "issue"))
}
}

Expand Down Expand Up @@ -573,9 +617,9 @@ func issueIndexPatternProcessor(ctx *postProcessCtx, node *html.Node) {
} else {
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 {
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)
}
Expand All @@ -591,7 +635,7 @@ func crossReferenceIssueIndexPatternProcessor(ctx *postProcessCtx, node *html.No
repo, issue := parts[0], parts[1]

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
Expand Down Expand Up @@ -642,7 +686,7 @@ func fullSha1PatternProcessor(ctx *postProcessCtx, node *html.Node) {
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
Expand Down Expand Up @@ -672,7 +716,7 @@ func sha1CurrentPatternProcessor(ctx *postProcessCtx, node *html.Node) {
}

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.
Expand All @@ -682,7 +726,7 @@ func emailAddressProcessor(ctx *postProcessCtx, node *html.Node) {
return
}
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
Expand All @@ -693,7 +737,7 @@ func linkProcessor(ctx *postProcessCtx, node *html.Node) {
return
}
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 {
Expand All @@ -707,7 +751,10 @@ func genDefaultLinkProcessor(defaultLink string) processor {
node.Type = html.ElementNode
node.Data = "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
}
}
Expand Down
36 changes: 20 additions & 16 deletions modules/markup/html_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@ const Repo = "gogits/gogs"
const AppSubURL = AppURL + Repo + "/"

// alphanumLink an HTML link to an alphanumeric-style issue
func alphanumIssueLink(baseURL string, name string) string {
return link(util.URLJoin(baseURL, name), name)
func alphanumIssueLink(baseURL, class, name string) string {
return link(util.URLJoin(baseURL, name), class, name)
}

// numericLink an HTML to a numeric-style issue
func numericIssueLink(baseURL string, index int) string {
return link(util.URLJoin(baseURL, strconv.Itoa(index)), fmt.Sprintf("#%d", index))
func numericIssueLink(baseURL, class string, index int) string {
return link(util.URLJoin(baseURL, strconv.Itoa(index)), class, fmt.Sprintf("#%d", index))
}

// link an HTML link
func link(href, contents string) string {
return fmt.Sprintf("<a href=\"%s\">%s</a>", href, contents)
func link(href, class, contents string) string {
if class != "" {
class = " class=\"" + class + "\""
}

return fmt.Sprintf("<a href=\"%s\"%s>%s</a>", href, class, contents)
}

var numericMetas = map[string]string{
Expand Down Expand Up @@ -89,13 +93,13 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
test := func(s, expectedFmt string, indices ...int) {
links := make([]interface{}, len(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...)
testRenderIssueIndexPattern(t, s, expectedNil, &postProcessCtx{metas: localMetas})

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...)
testRenderIssueIndexPattern(t, s, expectedNum, &postProcessCtx{metas: numericMetas})
Expand Down Expand Up @@ -158,7 +162,7 @@ func TestRender_IssueIndexPattern4(t *testing.T) {
test := func(s, expectedFmt string, names ...string) {
links := make([]interface{}, len(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...)
testRenderIssueIndexPattern(t, s, expected, &postProcessCtx{metas: alphanumericMetas})
Expand Down Expand Up @@ -197,17 +201,17 @@ func TestRender_AutoLink(t *testing.T) {

// render valid issue URLs
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
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"
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
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) {
Expand All @@ -228,11 +232,11 @@ func TestRender_FullIssueURLs(t *testing.T) {
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")
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",
`<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",
`<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) {
Expand Down
33 changes: 26 additions & 7 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,14 @@ func NewFuncMap() []template.FuncMap {
"EscapePound": func(str string) string {
return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str)
},
"PathEscapeSegments": util.PathEscapeSegments,
"URLJoin": util.URLJoin,
"RenderCommitMessage": RenderCommitMessage,
"RenderCommitMessageLink": RenderCommitMessageLink,
"RenderCommitBody": RenderCommitBody,
"RenderNote": RenderNote,
"IsMultilineCommitMessage": IsMultilineCommitMessage,
"PathEscapeSegments": util.PathEscapeSegments,
"URLJoin": util.URLJoin,
"RenderCommitMessage": RenderCommitMessage,
"RenderCommitMessageLink": RenderCommitMessageLink,
"RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject,
"RenderCommitBody": RenderCommitBody,
"RenderNote": RenderNote,
"IsMultilineCommitMessage": IsMultilineCommitMessage,
"ThemeColorMetaTag": func() string {
return setting.UI.ThemeColorMetaTag
},
Expand Down Expand Up @@ -322,6 +323,24 @@ func RenderCommitMessageLink(msg, urlPrefix, urlDefault string, metas map[string
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.
func RenderCommitBody(msg, urlPrefix string, metas map[string]string) template.HTML {
cleanMsg := template.HTMLEscapeString(msg)
Expand Down
8 changes: 8 additions & 0 deletions public/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 .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.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}
Expand Down Expand Up @@ -829,6 +833,10 @@ footer .ui.left,footer .ui.right{line-height:40px}
.stats-table .table-cell.tiny{height:.5em}
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 .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-body{white-space:pre-wrap}
.git-notes.top{text-align:left}
Expand Down
36 changes: 36 additions & 0 deletions public/less/_repository.less
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,24 @@
.ui.avatar {
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 {
Expand Down Expand Up @@ -2188,6 +2206,24 @@ tbody.commit-list {
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 {
display: inline-block;
vertical-align: middle;
Expand Down
3 changes: 2 additions & 1 deletion templates/repo/commits_table.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
</td>
<td class="message">
<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>
{{if IsMultilineCommitMessage .Message}}
<button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button>
Expand Down
Loading