mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-08-05 09:55:20 +02:00
feat(ui): add issue comment assignment doer links
This commit is contained in:
parent
39e6785da0
commit
1b9ac27578
5 changed files with 58 additions and 25 deletions
|
@ -188,6 +188,7 @@ func NewFuncMap() template.FuncMap {
|
||||||
"RenderMarkdownToHtml": RenderMarkdownToHtml,
|
"RenderMarkdownToHtml": RenderMarkdownToHtml,
|
||||||
"RenderLabel": RenderLabel,
|
"RenderLabel": RenderLabel,
|
||||||
"RenderLabels": RenderLabels,
|
"RenderLabels": RenderLabels,
|
||||||
|
"RenderUser": RenderUser,
|
||||||
"RenderReviewRequest": RenderReviewRequest,
|
"RenderReviewRequest": RenderReviewRequest,
|
||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
issues_model "forgejo.org/models/issues"
|
issues_model "forgejo.org/models/issues"
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
"forgejo.org/modules/emoji"
|
"forgejo.org/modules/emoji"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"forgejo.org/modules/markup"
|
"forgejo.org/modules/markup"
|
||||||
|
@ -26,7 +28,7 @@ import (
|
||||||
|
|
||||||
// RenderCommitMessage renders commit message with XSS-safe and special links.
|
// RenderCommitMessage renders commit message with XSS-safe and special links.
|
||||||
func RenderCommitMessage(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
func RenderCommitMessage(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
||||||
cleanMsg := template.HTMLEscapeString(msg)
|
cleanMsg := html.EscapeString(msg)
|
||||||
// we can safely assume that it will not return any error, since there
|
// we can safely assume that it will not return any error, since there
|
||||||
// shouldn't be any special HTML.
|
// shouldn't be any special HTML.
|
||||||
fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
||||||
|
@ -63,7 +65,7 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string,
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
DefaultLink: urlDefault,
|
DefaultLink: urlDefault,
|
||||||
Metas: metas,
|
Metas: metas,
|
||||||
}, template.HTMLEscapeString(msgLine))
|
}, html.EscapeString(msgLine))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderCommitMessageSubject: %v", err)
|
log.Error("RenderCommitMessageSubject: %v", err)
|
||||||
return template.HTML("")
|
return template.HTML("")
|
||||||
|
@ -88,7 +90,7 @@ func RenderCommitBody(ctx context.Context, msg string, metas map[string]string)
|
||||||
renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Metas: metas,
|
Metas: metas,
|
||||||
}, template.HTMLEscapeString(msgLine))
|
}, html.EscapeString(msgLine))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderCommitMessage: %v", err)
|
log.Error("RenderCommitMessage: %v", err)
|
||||||
return ""
|
return ""
|
||||||
|
@ -122,7 +124,7 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string)
|
||||||
renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
|
renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Metas: metas,
|
Metas: metas,
|
||||||
}, template.HTMLEscapeString(text))
|
}, html.EscapeString(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderIssueTitle: %v", err)
|
log.Error("RenderIssueTitle: %v", err)
|
||||||
return template.HTML("")
|
return template.HTML("")
|
||||||
|
@ -132,7 +134,7 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string)
|
||||||
|
|
||||||
// RenderRefIssueTitle renders referenced issue/pull title with defined post processors
|
// RenderRefIssueTitle renders referenced issue/pull title with defined post processors
|
||||||
func RenderRefIssueTitle(ctx context.Context, text string) template.HTML {
|
func RenderRefIssueTitle(ctx context.Context, text string) template.HTML {
|
||||||
renderedText, err := markup.RenderRefIssueTitle(&markup.RenderContext{Ctx: ctx}, template.HTMLEscapeString(text))
|
renderedText, err := markup.RenderRefIssueTitle(&markup.RenderContext{Ctx: ctx}, html.EscapeString(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderRefIssueTitle: %v", err)
|
log.Error("RenderRefIssueTitle: %v", err)
|
||||||
return ""
|
return ""
|
||||||
|
@ -150,7 +152,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m
|
||||||
labelScope = label.ExclusiveScope()
|
labelScope = label.ExclusiveScope()
|
||||||
)
|
)
|
||||||
|
|
||||||
description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description))
|
description := emoji.ReplaceAliases(html.EscapeString(label.Description))
|
||||||
|
|
||||||
if label.IsArchived() {
|
if label.IsArchived() {
|
||||||
archivedCSSClass = "archived-label"
|
archivedCSSClass = "archived-label"
|
||||||
|
@ -212,7 +214,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m
|
||||||
// RenderEmoji renders html text with emoji post processors
|
// RenderEmoji renders html text with emoji post processors
|
||||||
func RenderEmoji(ctx context.Context, text string) template.HTML {
|
func RenderEmoji(ctx context.Context, text string) template.HTML {
|
||||||
renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ctx},
|
renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ctx},
|
||||||
template.HTMLEscapeString(text))
|
html.EscapeString(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderEmoji: %v", err)
|
log.Error("RenderEmoji: %v", err)
|
||||||
return template.HTML("")
|
return template.HTML("")
|
||||||
|
@ -263,10 +265,20 @@ func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issu
|
||||||
return template.HTML(htmlCode)
|
return template.HTML(htmlCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RenderUser(ctx context.Context, user user_model.User) template.HTML {
|
||||||
|
if user.ID > 0 {
|
||||||
|
return template.HTML(fmt.Sprintf(
|
||||||
|
"<a href='%s' rel='nofollow'><strong>%s</strong></a>",
|
||||||
|
user.HomeLink(), html.EscapeString(user.GetDisplayName())))
|
||||||
|
}
|
||||||
|
return template.HTML(fmt.Sprintf("<strong>%s</strong>",
|
||||||
|
html.EscapeString(user.GetDisplayName())))
|
||||||
|
}
|
||||||
|
|
||||||
func RenderReviewRequest(users []issues_model.RequestReviewTarget) template.HTML {
|
func RenderReviewRequest(users []issues_model.RequestReviewTarget) template.HTML {
|
||||||
usernames := make([]string, 0, len(users))
|
usernames := make([]string, 0, len(users))
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
usernames = append(usernames, template.HTMLEscapeString(user.Name()))
|
usernames = append(usernames, html.EscapeString(user.Name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlCode := `<span class="review-request-list">`
|
htmlCode := `<span class="review-request-list">`
|
||||||
|
|
|
@ -11,6 +11,9 @@ import (
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
issues_model "forgejo.org/models/issues"
|
issues_model "forgejo.org/models/issues"
|
||||||
"forgejo.org/models/unittest"
|
"forgejo.org/models/unittest"
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/modules/setting"
|
||||||
|
"forgejo.org/modules/test"
|
||||||
"forgejo.org/modules/translation"
|
"forgejo.org/modules/translation"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -221,3 +224,26 @@ func TestRenderLabels(t *testing.T) {
|
||||||
assert.Contains(t, RenderLabels(db.DefaultContext, tr, []*issues_model.Label{label}, "user2/repo1", true),
|
assert.Contains(t, RenderLabels(db.DefaultContext, tr, []*issues_model.Label{label}, "user2/repo1", true),
|
||||||
"user2/repo1/pulls?labels=1")
|
"user2/repo1/pulls?labels=1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderUser(t *testing.T) {
|
||||||
|
unittest.PrepareTestEnv(t)
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||||
|
ghost := user_model.NewGhostUser()
|
||||||
|
|
||||||
|
assert.Contains(t, RenderUser(db.DefaultContext, *user),
|
||||||
|
"<a href='/user2' rel='nofollow'><strong>user2</strong></a>")
|
||||||
|
assert.Contains(t, RenderUser(db.DefaultContext, *org),
|
||||||
|
"<a href='/org3' rel='nofollow'><strong>org3</strong></a>")
|
||||||
|
assert.Contains(t, RenderUser(db.DefaultContext, *ghost),
|
||||||
|
"<strong>Ghost</strong>")
|
||||||
|
|
||||||
|
defer test.MockVariableValue(&setting.UI.DefaultShowFullName, true)()
|
||||||
|
assert.Contains(t, RenderUser(db.DefaultContext, *user),
|
||||||
|
"<a href='/user2' rel='nofollow'><strong>< U<se>r Tw<o > ><</strong></a>")
|
||||||
|
assert.Contains(t, RenderUser(db.DefaultContext, *org),
|
||||||
|
"<a href='/org3' rel='nofollow'><strong><<<< >> >> > >> > >>> >></strong></a>")
|
||||||
|
assert.Contains(t, RenderUser(db.DefaultContext, *ghost),
|
||||||
|
"<strong>Ghost</strong>")
|
||||||
|
}
|
||||||
|
|
|
@ -197,27 +197,23 @@
|
||||||
{{else if and (eq .Type 9) (gt .AssigneeID 0)}}
|
{{else if and (eq .Type 9) (gt .AssigneeID 0)}}
|
||||||
<div class="timeline-item event" id="{{.HashTag}}">
|
<div class="timeline-item event" id="{{.HashTag}}">
|
||||||
<span class="badge">{{svg "octicon-person"}}</span>
|
<span class="badge">{{svg "octicon-person"}}</span>
|
||||||
{{if .RemovedAssignee}}
|
|
||||||
{{template "shared/user/avatarlink" dict "user" .Assignee}}
|
{{template "shared/user/avatarlink" dict "user" .Assignee}}
|
||||||
<span class="text grey muted-links">
|
<span class="text grey muted-links">
|
||||||
{{template "shared/user/authorlink" .Assignee}}
|
{{template "shared/user/authorlink" .Assignee}}
|
||||||
|
{{if .RemovedAssignee}}
|
||||||
{{if eq .Poster.ID .Assignee.ID}}
|
{{if eq .Poster.ID .Assignee.ID}}
|
||||||
{{ctx.Locale.Tr "repo.issues.remove_self_assignment" $createdStr}}
|
{{ctx.Locale.Tr "repo.issues.remove_self_assignment" $createdStr}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{ctx.Locale.Tr "repo.issues.remove_assignee_at" .Poster.GetDisplayName $createdStr}}
|
{{ctx.Locale.Tr "repo.issues.remove_assignee_at" (RenderUser $.Context .Poster) $createdStr}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</span>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{template "shared/user/avatarlink" dict "user" .Assignee}}
|
|
||||||
<span class="text grey muted-links">
|
|
||||||
{{template "shared/user/authorlink" .Assignee}}
|
|
||||||
{{if eq .Poster.ID .AssigneeID}}
|
{{if eq .Poster.ID .AssigneeID}}
|
||||||
{{ctx.Locale.Tr "repo.issues.self_assign_at" $createdStr}}
|
{{ctx.Locale.Tr "repo.issues.self_assign_at" $createdStr}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{ctx.Locale.Tr "repo.issues.add_assignee_at" .Poster.GetDisplayName $createdStr}}
|
{{ctx.Locale.Tr "repo.issues.add_assignee_at" (RenderUser $.Context .Poster) $createdStr}}
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</span>
|
</span>
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
{{else if eq .Type 10}}
|
{{else if eq .Type 10}}
|
||||||
<div class="timeline-item event" id="{{.HashTag}}">
|
<div class="timeline-item event" id="{{.HashTag}}">
|
||||||
|
|
|
@ -205,15 +205,13 @@ func TestIssueCommentChangeAssignee(t *testing.T) {
|
||||||
testIssueCommentChangeEvent(t, htmlDoc, "2041",
|
testIssueCommentChangeEvent(t, htmlDoc, "2041",
|
||||||
"octicon-person", "User One", "/user1",
|
"octicon-person", "User One", "/user1",
|
||||||
[]string{"user1 was unassigned by user2"},
|
[]string{"user1 was unassigned by user2"},
|
||||||
[]string{"/user1"})
|
[]string{"/user1", "/user2"})
|
||||||
// []string{"/user1", "/user2"})
|
|
||||||
|
|
||||||
// Add other
|
// Add other
|
||||||
testIssueCommentChangeEvent(t, htmlDoc, "2042",
|
testIssueCommentChangeEvent(t, htmlDoc, "2042",
|
||||||
"octicon-person", "< U<se>r Tw<o > ><", "/user2",
|
"octicon-person", "< U<se>r Tw<o > ><", "/user2",
|
||||||
[]string{"user2 was assigned by user1"},
|
[]string{"user2 was assigned by user1"},
|
||||||
[]string{"/user2"})
|
[]string{"/user2", "/user1"})
|
||||||
// []string{"/user2", "/user1"})
|
|
||||||
|
|
||||||
// Self-remove
|
// Self-remove
|
||||||
testIssueCommentChangeEvent(t, htmlDoc, "2043",
|
testIssueCommentChangeEvent(t, htmlDoc, "2043",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue