aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
author赵智超 <1012112796@qq.com>2020-04-18 21:50:25 +0800
committerGitHub <noreply@github.com>2020-04-18 10:50:25 -0300
commit1b86f174ce99b579df35c32e800551b1fd1df807 (patch)
treee057206a13cea7a0e2eb0219bb66d8d83a8781c9
parent38d5f88a8183147cc5ecc6ece58fc28836a4af8b (diff)
downloadgitea-1b86f174ce99b579df35c32e800551b1fd1df807.tar.gz
gitea-1b86f174ce99b579df35c32e800551b1fd1df807.zip
Add a way to mark Conversation (code comment) resolved (#11037)
* Add a way to mark Conversation (code comment) resolved mark Conversation is a way to mark a Conversation is stale or be solved. when it's marked as stale, will be hided like stale. all Pull Request writer , Offical Reviewers and poster can add or remove Conversation resolved mark. Signed-off-by: a1012112796 <1012112796@qq.com> * fix lint * Apply suggestions from code review * Add ResolveDoer * fix ui Co-Authored-By: Lauris BH <lauris@nix.lv> Co-Authored-By: 6543 <6543@obermui.de> * change IsResolved to an function Add permission check in UpdateResolveConversation * Apply suggestions from code review * change return error for permisson check * add default message for deleted user * get issue message from comment * add migration for ``ResolveDoerID`` column another change: * block mark pending review as resolved because it's not necessary Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> * change button color * resolve button size * fix code style * remove unusefull code Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
-rw-r--r--models/issue_comment.go27
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/migrations/v138.go22
-rw-r--r--models/review.go60
-rw-r--r--options/locale/locale_en-US.ini5
-rw-r--r--routers/repo/issue.go10
-rw-r--r--routers/repo/pull.go7
-rw-r--r--routers/repo/pull_review.go47
-rw-r--r--routers/routes/routes.go1
-rw-r--r--templates/repo/diff/box.tmpl51
-rw-r--r--templates/repo/diff/section_unified.tmpl27
-rw-r--r--templates/repo/issue/view_content/comments.tmpl38
-rw-r--r--web_src/js/index.js13
13 files changed, 301 insertions, 9 deletions
diff --git a/models/issue_comment.go b/models/issue_comment.go
index 2e59a2cb3f..f7017435d7 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -122,6 +122,8 @@ type Comment struct {
AssigneeID int64
RemovedAssignee bool
Assignee *User `xorm:"-"`
+ ResolveDoerID int64
+ ResolveDoer *User `xorm:"-"`
OldTitle string
NewTitle string
OldRef string
@@ -420,6 +422,26 @@ func (c *Comment) LoadAssigneeUser() error {
return nil
}
+// LoadResolveDoer if comment.Type is CommentTypeCode and ResolveDoerID not zero, then load resolveDoer
+func (c *Comment) LoadResolveDoer() (err error) {
+ if c.ResolveDoerID == 0 || c.Type != CommentTypeCode {
+ return nil
+ }
+ c.ResolveDoer, err = getUserByID(x, c.ResolveDoerID)
+ if err != nil {
+ if IsErrUserNotExist(err) {
+ c.ResolveDoer = NewGhostUser()
+ err = nil
+ }
+ }
+ return
+}
+
+// IsResolved check if an code comment is resolved
+func (c *Comment) IsResolved() bool {
+ return c.ResolveDoerID != 0 && c.Type == CommentTypeCode
+}
+
// LoadDepIssueDetails loads Dependent Issue Details
func (c *Comment) LoadDepIssueDetails() (err error) {
if c.DependentIssueID <= 0 || c.DependentIssue != nil {
@@ -943,7 +965,12 @@ func fetchCodeCommentsByReview(e Engine, issue *Issue, currentUser *User, review
if err := e.In("id", ids).Find(&reviews); err != nil {
return nil, err
}
+
for _, comment := range comments {
+ if err := comment.LoadResolveDoer(); err != nil {
+ return nil, err
+ }
+
if re, ok := reviews[comment.ReviewID]; ok && re != nil {
// If the review is pending only the author can see the comments (except the review is set)
if review.ID == 0 {
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index fe72d0f630..6868aad7b1 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -208,6 +208,8 @@ var migrations = []Migration{
NewMigration("Add CommitsAhead and CommitsBehind Column to PullRequest Table", addCommitDivergenceToPulls),
// v137 -> v138
NewMigration("Add Branch Protection Block Outdated Branch", addBlockOnOutdatedBranch),
+ // v138 -> v139
+ NewMigration("Add ResolveDoerID to Comment table", addResolveDoerIDCommentColumn),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v138.go b/models/migrations/v138.go
new file mode 100644
index 0000000000..2db9b821ad
--- /dev/null
+++ b/models/migrations/v138.go
@@ -0,0 +1,22 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func addResolveDoerIDCommentColumn(x *xorm.Engine) error {
+ type Comment struct {
+ ResolveDoerID int64
+ }
+
+ if err := x.Sync2(new(Comment)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
+ }
+ return nil
+}
diff --git a/models/review.go b/models/review.go
index d6bb77925e..7f23777c3e 100644
--- a/models/review.go
+++ b/models/review.go
@@ -5,6 +5,7 @@
package models
import (
+ "fmt"
"strings"
"code.gitea.io/gitea/modules/timeutil"
@@ -594,3 +595,62 @@ func RemoveRewiewRequest(issue *Issue, reviewer *User, doer *User) (comment *Com
return comment, sess.Commit()
}
+
+// MarkConversation Add or remove Conversation mark for a code comment
+func MarkConversation(comment *Comment, doer *User, isResolve bool) (err error) {
+ if comment.Type != CommentTypeCode {
+ return nil
+ }
+
+ if isResolve {
+ if comment.ResolveDoerID != 0 {
+ return nil
+ }
+
+ if _, err = x.Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", doer.ID, comment.ID); err != nil {
+ return err
+ }
+ } else {
+ if comment.ResolveDoerID == 0 {
+ return nil
+ }
+
+ if _, err = x.Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", 0, comment.ID); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// CanMarkConversation Add or remove Conversation mark for a code comment permission check
+// the PR writer , offfcial reviewer and poster can do it
+func CanMarkConversation(issue *Issue, doer *User) (permResult bool, err error) {
+ if doer == nil || issue == nil {
+ return false, fmt.Errorf("issue or doer is nil")
+ }
+
+ if doer.ID != issue.PosterID {
+ if err = issue.LoadRepo(); err != nil {
+ return false, err
+ }
+
+ perm, err := GetUserRepoPermission(issue.Repo, doer)
+ if err != nil {
+ return false, err
+ }
+
+ permResult = perm.CanAccess(AccessModeWrite, UnitTypePullRequests)
+ if !permResult {
+ if permResult, err = IsOfficialReviewer(issue, doer); err != nil {
+ return false, err
+ }
+ }
+
+ if !permResult {
+ return false, nil
+ }
+ }
+
+ return true, nil
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 9653784f91..cfad41ceb5 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1067,6 +1067,11 @@ issues.review.review = Review
issues.review.reviewers = Reviewers
issues.review.show_outdated = Show outdated
issues.review.hide_outdated = Hide outdated
+issues.review.show_resolved = Show resolved
+issues.review.hide_resolved = Hide resolved
+issues.review.resolve_conversation = Resolve conversation
+issues.review.un_resolve_conversation = Unresolve conversation
+issues.review.resolved_by = marked this conversation as resolved
issues.assignee.error = Not all assignees was added due to an unexpected error.
pulls.desc = Enable pull requests and code reviews.
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index 9ad379684a..a7fda4e769 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -990,6 +990,11 @@ func ViewIssue(ctx *context.Context) {
ctx.ServerError("Review.LoadCodeComments", err)
return
}
+
+ if err = comment.LoadResolveDoer(); err != nil {
+ ctx.ServerError("LoadResolveDoer", err)
+ return
+ }
}
}
@@ -1033,6 +1038,11 @@ func ViewIssue(ctx *context.Context) {
ctx.ServerError("IsUserAllowedToMerge", err)
return
}
+
+ if ctx.Data["CanMarkConversation"], err = models.CanMarkConversation(issue, ctx.User); err != nil {
+ ctx.ServerError("CanMarkConversation", err)
+ return
+ }
}
prUnit, err := repo.GetUnit(models.UnitTypePullRequests)
diff --git a/routers/repo/pull.go b/routers/repo/pull.go
index 63cc39865c..d23c93d0b6 100644
--- a/routers/repo/pull.go
+++ b/routers/repo/pull.go
@@ -624,6 +624,13 @@ func ViewPullFiles(ctx *context.Context) {
return
}
+ if ctx.IsSigned && ctx.User != nil {
+ if ctx.Data["CanMarkConversation"], err = models.CanMarkConversation(issue, ctx.User); err != nil {
+ ctx.ServerError("CanMarkConversation", err)
+ return
+ }
+ }
+
setImageCompareContext(ctx, baseCommit, commit)
setPathsCompareContext(ctx, baseCommit, commit, headTarget)
diff --git a/routers/repo/pull_review.go b/routers/repo/pull_review.go
index 0f5375dc16..730074b7f3 100644
--- a/routers/repo/pull_review.go
+++ b/routers/repo/pull_review.go
@@ -61,6 +61,53 @@ func CreateCodeComment(ctx *context.Context, form auth.CodeCommentForm) {
ctx.Redirect(comment.HTMLURL())
}
+// UpdateResolveConversation add or remove an Conversation resolved mark
+func UpdateResolveConversation(ctx *context.Context) {
+ action := ctx.Query("action")
+ commentID := ctx.QueryInt64("comment_id")
+
+ comment, err := models.GetCommentByID(commentID)
+ if err != nil {
+ ctx.ServerError("GetIssueByID", err)
+ return
+ }
+
+ if err = comment.LoadIssue(); err != nil {
+ ctx.ServerError("comment.LoadIssue", err)
+ return
+ }
+
+ var permResult bool
+ if permResult, err = models.CanMarkConversation(comment.Issue, ctx.User); err != nil {
+ ctx.ServerError("CanMarkConversation", err)
+ return
+ }
+ if !permResult {
+ ctx.Error(403)
+ return
+ }
+
+ if !comment.Issue.IsPull {
+ ctx.Error(400)
+ return
+ }
+
+ if action == "Resolve" || action == "UnResolve" {
+ err = models.MarkConversation(comment, ctx.User, action == "Resolve")
+ if err != nil {
+ ctx.ServerError("MarkConversation", err)
+ return
+ }
+ } else {
+ ctx.Error(400)
+ return
+ }
+
+ ctx.JSON(200, map[string]interface{}{
+ "ok": true,
+ })
+}
+
// SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) {
issue := GetActionIssue(ctx)
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index 2273cb4473..e2514054bf 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -739,6 +739,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee)
m.Post("/request_review", reqRepoIssuesOrPullsReader, repo.UpdatePullReviewRequest)
m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
+ m.Post("/resolve_conversation", reqRepoIssuesOrPullsReader, repo.UpdateResolveConversation)
}, context.RepoMustNotBeArchived())
m.Group("/comments/:id", func() {
m.Post("", repo.UpdateCommentContent)
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl
index d113f9ecee..a789350525 100644
--- a/templates/repo/diff/box.tmpl
+++ b/templates/repo/diff/box.tmpl
@@ -147,32 +147,79 @@
{{end}}
</tr>
{{if gt (len $line.Comments) 0}}
+ {{$resolved := (index $line.Comments 0).IsResolved}}
+ {{$resolveDoer := (index $line.Comments 0).ResolveDoer}}
+ {{$isNotPending := (not (eq (index $line.Comments 0).Review.Type 0))}}
<tr class="add-code-comment">
<td class="lines-num"></td>
<td class="lines-type-marker"></td>
<td class="add-comment-left">
+ {{if and $resolved (eq $line.GetCommentSide "previous")}}
+ <div class="ui top attached header">
+ <span class="ui grey text left"><b>{{$resolveDoer.Name}}</b> {{$.i18n.Tr "repo.issues.review.resolved_by"}}</span>
+ <button id="show-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="ui compact right labeled button show-outdated">
+ {{svg "octicon-unfold" 16}}
+ {{$.i18n.Tr "repo.issues.review.show_resolved"}}
+ </button>
+ <button id="hide-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="hide ui compact right labeled button hide-outdated">
+ {{svg "octicon-fold" 16}}
+ {{$.i18n.Tr "repo.issues.review.hide_resolved"}}
+ </button>
+ </div>
+ {{end}}
{{if eq $line.GetCommentSide "previous"}}
- <div class="field comment-code-cloud">
+ <div id="code-comments-{{(index $line.Comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}hide{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{ template "repo/diff/comments" dict "root" $ "comments" $line.Comments}}
</ui>
</div>
{{template "repo/diff/comment_form_datahandler" dict "reply" (index $line.Comments 0).ReviewID "hidden" true "root" $ "comment" (index $line.Comments 0)}}
+ {{if and $.CanMarkConversation $isNotPending}}
+ <button class="ui icon tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index $line.Comments 0).ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation" >
+ {{if $resolved}}
+ {{$.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
+ {{else}}
+ {{$.i18n.Tr "repo.issues.review.resolve_conversation"}}
+ {{end}}
+ </button>
+ {{end}}
</div>
{{end}}
</td>
<td class="lines-num"></td>
<td class="lines-type-marker"></td>
<td class="add-comment-right">
+ {{if and $resolved (eq $line.GetCommentSide "proposed")}}
+ <div class="ui top attached header">
+ <span class="ui grey text left"><b>{{$resolveDoer.Name}}</b> {{$.i18n.Tr "repo.issues.review.resolved_by"}}</span>
+ <button id="show-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="ui compact right labeled button show-outdated">
+ {{svg "octicon-unfold" 16}}
+ {{$.i18n.Tr "repo.issues.review.show_resolved"}}
+ </button>
+ <button id="hide-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="hide ui compact right labeled button hide-outdated">
+ {{svg "octicon-fold" 16}}
+ {{$.i18n.Tr "repo.issues.review.hide_resolved"}}
+ </button>
+ </div>
+ {{end}}
{{if eq $line.GetCommentSide "proposed"}}
- <div class="field comment-code-cloud">
+ <div id="code-comments-{{(index $line.Comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}hide{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{ template "repo/diff/comments" dict "root" $ "comments" $line.Comments}}
</ui>
</div>
{{template "repo/diff/comment_form_datahandler" dict "reply" (index $line.Comments 0).ReviewID "hidden" true "root" $ "comment" (index $line.Comments 0)}}
+ {{if and $.CanMarkConversation $isNotPending}}
+ <button class="ui icon tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index $line.Comments 0).ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation" >
+ {{if $resolved}}
+ {{$.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
+ {{else}}
+ {{$.i18n.Tr "repo.issues.review.resolve_conversation"}}
+ {{end}}
+ </button>
+ {{end}}
</div>
{{end}}
</td>
diff --git a/templates/repo/diff/section_unified.tmpl b/templates/repo/diff/section_unified.tmpl
index c25d3ef079..4c23c159a2 100644
--- a/templates/repo/diff/section_unified.tmpl
+++ b/templates/repo/diff/section_unified.tmpl
@@ -23,17 +23,42 @@
<td class="lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}">{{if and $.root.SignedUserID $line.CanComment $.root.PageIsPullFiles}}<a class="ui green button add-code-comment add-code-comment-{{if $line.RightIdx}}right{{else}}left{{end}}" data-path="{{$file.Name}}" data-side="{{if $line.RightIdx}}right{{else}}left{{end}}" data-idx="{{if $line.RightIdx}}{{$line.RightIdx}}{{else}}{{$line.LeftIdx}}{{end}}">+</a>{{end}}<span class="mono wrap{{if $highlightClass}} language-{{$highlightClass}}{{else}} nohighlight{{end}}">{{$section.GetComputedInlineDiffFor $line}}</span></td>
</tr>
{{if gt (len $line.Comments) 0}}
+ {{$resolved := (index $line.Comments 0).IsResolved}}
+ {{$resolveDoer := (index $line.Comments 0).ResolveDoer}}
+ {{$isNotPending := (not (eq (index $line.Comments 0).Review.Type 0))}}
<tr>
<td colspan="2" class="lines-num"></td>
<td class="lines-type-marker"></td>
<td class="add-comment-left add-comment-right">
- <div class="field comment-code-cloud">
+ {{if $resolved}}
+ <div class = "ui attached header">
+ <span class="ui grey text left"><b>{{$resolveDoer.Name}}</b> {{$.root.i18n.Tr "repo.issues.review.resolved_by"}}</span>
+ <button id="show-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="ui compact right labeled button show-outdated">
+ {{svg "octicon-unfold" 16}}
+ {{$.root.i18n.Tr "repo.issues.review.show_resolved"}}
+ </button>
+ <button id="hide-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="hide ui compact right labeled button hide-outdated">
+ {{svg "octicon-fold" 16}}
+ {{$.root.i18n.Tr "repo.issues.review.hide_resolved"}}
+ </button>
+ </div>
+ {{end}}
+ <div id="code-comments-{{(index $line.Comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}hide{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{ template "repo/diff/comments" dict "root" $.root "comments" $line.Comments}}
</ui>
</div>
{{template "repo/diff/comment_form_datahandler" dict "hidden" true "reply" (index $line.Comments 0).ReviewID "root" $.root "comment" (index $line.Comments 0)}}
+ {{if and $.root.CanMarkConversation $isNotPending}}
+ <button class="ui icon tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index $line.Comments 0).ID}}" data-update-url="{{$.root.RepoLink}}/issues/resolve_conversation" >
+ {{if $resolved}}
+ {{$.root.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
+ {{else}}
+ {{$.root.i18n.Tr "repo.issues.review.resolve_conversation"}}
+ {{end}}
+ </button>
+ {{end}}
</div>
</td>
</tr>
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index da1483ec37..27baaed3f2 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -381,6 +381,7 @@
</div>
</div>
{{end}}
+
{{if .Review.CodeComments}}
<div class="timeline-item event">
{{ range $filename, $lines := .Review.CodeComments}}
@@ -388,14 +389,25 @@
<div class="ui segments">
<div class="ui segment">
{{$invalid := (index $comms 0).Invalidated}}
- {{if $invalid}}
+ {{$resolved := (index $comms 0).IsResolved}}
+ {{$resolveDoer := (index $comms 0).ResolveDoer}}
+ {{$isNotPending := (not (eq (index $comms 0).Review.Type 0))}}
+ {{if or $invalid $resolved}}
<button id="show-outdated-{{(index $comms 0).ID}}" data-comment="{{(index $comms 0).ID}}" class="ui compact right labeled button show-outdated">
- {{svg "octicon-fold" 16}}
- {{$.i18n.Tr "repo.issues.review.show_outdated"}}
+ {{svg "octicon-unfold" 16}}
+ {{if $invalid }}
+ {{$.i18n.Tr "repo.issues.review.show_outdated"}}
+ {{else}}
+ {{$.i18n.Tr "repo.issues.review.show_resolved"}}
+ {{end}}
</button>
<button id="hide-outdated-{{(index $comms 0).ID}}" data-comment="{{(index $comms 0).ID}}" class="hide ui compact right labeled button hide-outdated">
{{svg "octicon-fold" 16}}
- {{$.i18n.Tr "repo.issues.review.hide_outdated"}}
+ {{if $invalid}}
+ {{$.i18n.Tr "repo.issues.review.hide_outdated"}}
+ {{else}}
+ {{$.i18n.Tr "repo.issues.review.hide_resolved"}}
+ {{end}}
</button>
{{end}}
<a href="{{(index $comms 0).CodeCommentURL}}" class="file-comment">{{$filename}}</a>
@@ -403,7 +415,7 @@
{{$diff := (CommentMustAsDiff (index $comms 0))}}
{{if $diff}}
{{$file := (index $diff.Files 0)}}
- <div id="code-preview-{{(index $comms 0).ID}}" class="ui table segment{{if $invalid}} hide{{end}}">
+ <div id="code-preview-{{(index $comms 0).ID}}" class="ui table segment{{if or $invalid $resolved}} hide{{end}}">
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}}">
<div class="file-body file-code code-view code-diff code-diff-unified">
<table>
@@ -415,7 +427,7 @@
</div>
</div>
{{end}}
- <div id="code-comments-{{(index $comms 0).ID}}" class="ui segment{{if $invalid}} hide{{end}}">
+ <div id="code-comments-{{(index $comms 0).ID}}" class="ui segment{{if or $invalid $resolved}} hide{{end}}">
<div class="ui comments">
{{range $comms}}
{{ $createdSubStr:= TimeSinceUnix .CreatedUnix $.Lang }}
@@ -445,6 +457,20 @@
{{end}}
</div>
{{template "repo/diff/comment_form_datahandler" dict "hidden" true "reply" (index $comms 0).ReviewID "root" $ "comment" (index $comms 0)}}
+
+ {{if and $.CanMarkConversation $isNotPending}}
+ <button class="ui tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index $comms 0).ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation" >
+ {{if $resolved}}
+ {{$.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
+ {{else}}
+ {{$.i18n.Tr "repo.issues.review.resolve_conversation"}}
+ {{end}}
+ </button>
+ {{end}}
+
+ {{if $resolved}}
+ <span class="ui grey text"><b>{{$resolveDoer.Name}}</b> {{$.i18n.Tr "repo.issues.review.resolved_by"}}</span>
+ {{end}}
</div>
</div>
{{end}}
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 2203ab7243..02de3b0068 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -2566,6 +2566,19 @@ $(document).ready(async () => {
$(e).click();
});
+ $('.resolve-conversation').on('click', function (e) {
+ e.preventDefault();
+ const id = $(this).data('comment-id');
+ const action = $(this).data('action');
+ const url = $(this).data('update-url');
+
+ $.post(url, {
+ _csrf: csrf,
+ action,
+ comment_id: id,
+ }).then(reload);
+ });
+
buttonsClickOnEnter();
searchUsers();
searchTeams();