diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index 67a77ceb13f36..fdba7dca25310 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -18,11 +18,11 @@ import ( type CodeComments map[string]map[int64][]*Comment // FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line -func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User, showOutdatedComments bool) (CodeComments, error) { - return fetchCodeCommentsByReview(ctx, issue, currentUser, nil, showOutdatedComments) +func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User, showOutdatedComments bool, filePath *string) (CodeComments, error) { + return fetchCodeCommentsByReview(ctx, issue, currentUser, nil, showOutdatedComments, filePath) } -func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review, showOutdatedComments bool) (CodeComments, error) { +func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review, showOutdatedComments bool, filePath *string) (CodeComments, error) { pathToLineToComment := make(CodeComments) if review == nil { review = &Review{ID: 0} @@ -33,6 +33,15 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u ReviewID: review.ID, } + if filePath != nil { + opts = FindCommentsOptions{ + Type: CommentTypeCode, + IssueID: issue.ID, + ReviewID: review.ID, + TreePath: *filePath, + } + } + comments, err := findCodeComments(ctx, opts, issue, currentUser, review, showOutdatedComments) if err != nil { return nil, err diff --git a/models/issues/comment_test.go b/models/issues/comment_test.go index c5bbfdedc28ec..10a4558639e8f 100644 --- a/models/issues/comment_test.go +++ b/models/issues/comment_test.go @@ -50,7 +50,7 @@ func TestFetchCodeComments(t *testing.T) { issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - res, err := issues_model.FetchCodeComments(db.DefaultContext, issue, user, false) + res, err := issues_model.FetchCodeComments(db.DefaultContext, issue, user, false, nil) assert.NoError(t, err) assert.Contains(t, res, "README.md") assert.Contains(t, res["README.md"], int64(4)) @@ -58,7 +58,7 @@ func TestFetchCodeComments(t *testing.T) { assert.Equal(t, int64(4), res["README.md"][4][0].ID) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - res, err = issues_model.FetchCodeComments(db.DefaultContext, issue, user2, false) + res, err = issues_model.FetchCodeComments(db.DefaultContext, issue, user2, false, nil) assert.NoError(t, err) assert.Len(t, res, 1) } diff --git a/models/issues/review.go b/models/issues/review.go index 8b345e5fd8002..12425172617f9 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -158,7 +158,7 @@ func (r *Review) LoadCodeComments(ctx context.Context) (err error) { if err = r.LoadIssue(ctx); err != nil { return err } - r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r, false) + r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r, false, nil) return err } diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 278974bec3ecf..4cd2f1006aa7d 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -14,6 +14,7 @@ import ( "net/http" "net/url" "path/filepath" + "sort" "strings" "code.gitea.io/gitea/models/db" @@ -39,6 +40,7 @@ import ( "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context/upload" "code.gitea.io/gitea/services/gitdiff" + user_service "code.gitea.io/gitea/services/user" ) const ( @@ -863,6 +865,10 @@ func ExcerptBlob(ctx *context.Context) { direction := ctx.FormString("direction") filePath := ctx.FormString("path") gitRepo := ctx.Repo.GitRepo + if ctx.FormBool("pull") { + ctx.Data["PageIsPullFiles"] = true + } + if ctx.FormBool("wiki") { var err error gitRepo, err = gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository) @@ -881,6 +887,7 @@ func ExcerptBlob(ctx *context.Context) { section := &gitdiff.DiffSection{ FileName: filePath, Name: filePath, + Lines: []*gitdiff.DiffLine{}, } if direction == "up" && (idxLeft-lastLeft) > chunkSize { idxLeft -= chunkSize @@ -924,7 +931,9 @@ func ExcerptBlob(ctx *context.Context) { RightIdx: idxRight, LeftHunkSize: leftHunkSize, RightHunkSize: rightHunkSize, + HasComments: false, }, + Comments: nil, } if direction == "up" { section.Lines = append([]*gitdiff.DiffLine{lineSection}, section.Lines...) @@ -932,10 +941,69 @@ func ExcerptBlob(ctx *context.Context) { section.Lines = append(section.Lines, lineSection) } } + issueIndex := ctx.FormInt64("issue_index") + if ctx.FormBool("pull") && issueIndex > 0 { + issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, issueIndex) + if err != nil { + ctx.ServerError("GetIssueByIndex", err) + return + } + allComments, err := issues_model.FetchCodeComments(ctx, issue, ctx.Doer, false, &filePath) + if err != nil { + ctx.ServerError("FetchCodeComments", err) + return + } + lineCommits := allComments[filePath] + for index, line := range section.Lines { + if line.SectionInfo != nil && line.Type == 4 && !(line.SectionInfo.LastRightIdx == 0 && index+1 == len(section.Lines)) { + start := int64(line.SectionInfo.LastRightIdx + 1) + end := int64(line.SectionInfo.RightIdx - 1) + for start <= end { + if _, ok := lineCommits[start]; ok { + if !line.SectionInfo.HasComments { + line.SectionInfo.HasComments = true + break + } + } + start++ + } + } + if comments, ok := lineCommits[int64(line.LeftIdx*-1)]; ok { + line.Comments = append(line.Comments, comments...) + } + if comments, ok := lineCommits[int64(line.RightIdx)]; ok { + line.Comments = append(line.Comments, comments...) + } + + sort.SliceStable(line.Comments, func(i, j int) bool { + return line.Comments[i].CreatedUnix < line.Comments[j].CreatedUnix + }) + } + for _, line := range section.Lines { + for _, comment := range line.Comments { + if err := comment.LoadAttachments(ctx); err != nil { + ctx.ServerError("LoadAttachments", err) + return + } + } + } + ctx.Data["Issue"] = issue + ctx.Data["IssueIndex"] = issue.Index + } ctx.Data["section"] = section ctx.Data["FileNameHash"] = git.HashFilePathForWebUI(filePath) ctx.Data["AfterCommitID"] = commitID ctx.Data["Anchor"] = anchor + ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool { + return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee) + } + if ctx.Data["SignedUserID"] == nil { + ctx.Data["SignedUserID"] = ctx.Doer.ID + } + ctx.Data["SignedUser"] = ctx.Doer + ctx.Data["IsSigned"] = ctx.Doer != nil + ctx.Data["Repository"] = ctx.Repo.Repository + ctx.Data["Permission"] = &ctx.Repo.Permission ctx.HTML(http.StatusOK, tplBlobExcerpt) } @@ -964,6 +1032,7 @@ func getExcerptLines(commit *git.Commit, filePath string, idxLeft, idxRight, chu RightIdx: line + 1, Type: gitdiff.DiffLinePlain, Content: " " + lineText, + Comments: []*issues_model.Comment{}, } diffLines = append(diffLines, diffLine) } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 9694ae845b1b9..609dfc0517aea 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -128,6 +128,7 @@ func getPullInfo(ctx *context.Context) (issue *issues_model.Issue, ok bool) { } ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, emoji.ReplaceAliases(issue.Title)) ctx.Data["Issue"] = issue + ctx.Data["IssueIndex"] = issue.Index if !issue.IsPull { ctx.NotFound("ViewPullCommits", nil) diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index bb1722039e19d..9cc1cc15a35a6 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -93,6 +93,7 @@ type DiffLineSectionInfo struct { RightIdx int LeftHunkSize int RightHunkSize int + HasComments bool } // BlobExcerptChunkSize represent max lines of excerpt @@ -177,6 +178,7 @@ func getDiffLineSectionInfo(treePath, line string, lastLeftIdx, lastRightIdx int RightIdx: rightLine, LeftHunkSize: leftHunk, RightHunkSize: righHunk, + HasComments: false, } } @@ -401,6 +403,7 @@ func (diffFile *DiffFile) GetTailSection(gitRepo *git.Repository, leftCommit, ri LastRightIdx: lastLine.RightIdx, LeftIdx: leftLineCount, RightIdx: rightLineCount, + HasComments: false, }, } tailSection := &DiffSection{FileName: diffFile.Name, Lines: []*DiffLine{tailDiffLine}} @@ -460,14 +463,28 @@ type Diff struct { // LoadComments loads comments into each line func (diff *Diff) LoadComments(ctx context.Context, issue *issues_model.Issue, currentUser *user_model.User, showOutdatedComments bool) error { - allComments, err := issues_model.FetchCodeComments(ctx, issue, currentUser, showOutdatedComments) + allComments, err := issues_model.FetchCodeComments(ctx, issue, currentUser, showOutdatedComments, nil) if err != nil { return err } + for _, file := range diff.Files { if lineCommits, ok := allComments[file.Name]; ok { for _, section := range file.Sections { - for _, line := range section.Lines { + for index, line := range section.Lines { + if line.SectionInfo != nil && line.Type == 4 && !(line.SectionInfo.LastRightIdx == 0 && index+1 == len(section.Lines)) { + start := int64(line.SectionInfo.LastRightIdx + 1) + end := int64(line.SectionInfo.RightIdx - 1) + for start <= end { + if _, ok := lineCommits[start]; ok { + if !line.SectionInfo.HasComments { + line.SectionInfo.HasComments = true + break + } + } + start++ + } + } if comments, ok := lineCommits[int64(line.LeftIdx*-1)]; ok { line.Comments = append(line.Comments, comments...) } diff --git a/services/repository/files/diff_test.go b/services/repository/files/diff_test.go index ea6ffe60c3f88..c698c8389265a 100644 --- a/services/repository/files/diff_test.go +++ b/services/repository/files/diff_test.go @@ -66,6 +66,7 @@ func TestGetDiffPreview(t *testing.T) { RightIdx: 1, LeftHunkSize: 3, RightHunkSize: 4, + HasComments: false, }, }, { diff --git a/templates/repo/diff/blob_excerpt.tmpl b/templates/repo/diff/blob_excerpt.tmpl index cc2237029bd68..ba4e29ad43eb2 100644 --- a/templates/repo/diff/blob_excerpt.tmpl +++ b/templates/repo/diff/blob_excerpt.tmpl @@ -1,35 +1,49 @@ {{if $.IsSplitStyle}} {{range $k, $line := $.section.Lines}} + {{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}} {{if eq .GetType 4}} {{$expandDirection := $line.GetExpandDirection}} -
- {{if or (eq $expandDirection 3) (eq $expandDirection 5)}} - - {{end}} - {{if or (eq $expandDirection 3) (eq $expandDirection 4)}} - - {{end}} - {{if eq $expandDirection 2}} - - {{end}} +
+
+ {{if $line.SectionInfo.HasComments}} + + {{end}} +
+
+ {{if or (eq $expandDirection 3) (eq $expandDirection 5)}} + + {{end}} + {{if or (eq $expandDirection 3) (eq $expandDirection 4)}} + + {{end}} + {{if eq $expandDirection 2}} + + {{end}} +
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}{{/* */}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}} {{else}} - {{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}} {{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}{{end}} {{if $line.LeftIdx}}{{end}} {{/* + */}}{{if and $.SignedUserID $.PageIsPullFiles}}{{/* + */}}{{/* + */}}{{end}}{{/* */}}{{if $line.LeftIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/* */}}{{/* */}}{{end}}{{/* @@ -38,45 +52,87 @@ {{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}{{end}} {{if $line.RightIdx}}{{end}} {{/* + */}}{{if and $.SignedUserID $.PageIsPullFiles}}{{/* + */}}{{/* + */}}{{end}}{{/* */}}{{if $line.RightIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/* */}}{{/* */}}{{end}}{{/* */}} {{end}} + {{if $line.Comments}} + + + {{template "repo/diff/conversation" dict "." $ "comments" $line.Comments}}} + + + {{end}} {{end}} {{else}} {{range $k, $line := $.section.Lines}} + {{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}} {{if eq .GetType 4}} {{$expandDirection := $line.GetExpandDirection}} -
- {{if or (eq $expandDirection 3) (eq $expandDirection 5)}} - - {{end}} - {{if or (eq $expandDirection 3) (eq $expandDirection 4)}} - - {{end}} - {{if eq $expandDirection 2}} - - {{end}} +
+
+ {{if $line.SectionInfo.HasComments}} + + {{end}} +
+
+ {{if or (eq $expandDirection 3) (eq $expandDirection 5)}} + + {{end}} + {{if or (eq $expandDirection 3) (eq $expandDirection 4)}} + + {{end}} + {{if eq $expandDirection 2}} + + {{end}} +
{{else}} {{end}} - {{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}} {{if $inlineDiff.EscapeStatus.Escaped}}{{end}} - {{$inlineDiff.Content}} + + {{if and $.SignedUserID $.PageIsPullFiles}} + + {{end}} + {{$inlineDiff.Content}} + + {{if $line.Comments}} + + + {{template "repo/diff/conversation" dict "." $ "comments" $line.Comments}} + + + + + {{end}} {{end}} {{end}} diff --git a/templates/repo/diff/section_split.tmpl b/templates/repo/diff/section_split.tmpl index 37b42bcb376ab..ce1f626d905c8 100644 --- a/templates/repo/diff/section_split.tmpl +++ b/templates/repo/diff/section_split.tmpl @@ -1,5 +1,6 @@ {{$file := .file}} {{$blobExcerptRepoLink := or ctx.RootData.CommitRepoLink ctx.RootData.RepoLink}} +{{$issueIndex := or ctx.RootData.IssueIndex $.root.IssueIndex}} @@ -18,22 +19,31 @@ {{if eq .GetType 4}} {{$expandDirection := $line.GetExpandDirection}} -
- {{if or (eq $expandDirection 3) (eq $expandDirection 5)}} - - {{end}} - {{if or (eq $expandDirection 3) (eq $expandDirection 4)}} - - {{end}} - {{if eq $expandDirection 2}} - - {{end}} +
+
+ {{if $line.SectionInfo.HasComments}} + + {{end}} +
+
+ {{if or (eq $expandDirection 3) (eq $expandDirection 5)}} + + {{end}} + {{if or (eq $expandDirection 3) (eq $expandDirection 4)}} + + {{end}} + {{if eq $expandDirection 2}} + + {{end}} +
{{$inlineDiff := $section.GetComputedInlineDiffFor $line ctx.Locale}} {{if $inlineDiff.EscapeStatus.Escaped}}{{end}} diff --git a/templates/repo/diff/section_unified.tmpl b/templates/repo/diff/section_unified.tmpl index 708b333291641..8ca2c8cc4f8ae 100644 --- a/templates/repo/diff/section_unified.tmpl +++ b/templates/repo/diff/section_unified.tmpl @@ -1,5 +1,6 @@ {{$file := .file}} {{$blobExcerptRepoLink := or ctx.RootData.CommitRepoLink ctx.RootData.RepoLink}} +{{$issueIndex := or ctx.RootData.IssueIndex $.root.IssueIndex}} @@ -14,22 +15,31 @@ {{if $.root.AfterCommitID}} {{$expandDirection := $line.GetExpandDirection}} -
- {{if or (eq $expandDirection 3) (eq $expandDirection 5)}} - - {{end}} - {{if or (eq $expandDirection 3) (eq $expandDirection 4)}} - - {{end}} - {{if eq $expandDirection 2}} - - {{end}} +
+
+ {{if $line.SectionInfo.HasComments}} + + {{end}} +
+
+ {{if or (eq $expandDirection 3) (eq $expandDirection 5)}} + + {{end}} + {{if or (eq $expandDirection 3) (eq $expandDirection 4)}} + + {{end}} + {{if eq $expandDirection 2}} + + {{end}} +
{{else}} diff --git a/web_src/css/base.css b/web_src/css/base.css index 04f3678f3a71a..f01f1bedc7434 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -1141,6 +1141,17 @@ overflow-menu .ui.label { font-family: var(--fonts-regular); } +.lines-comment { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1px; +} + +.section-comment-icon { + cursor: default; +} + .lines-commit .blame-info .blame-data .blame-message { flex-grow: 2; overflow: hidden; diff --git a/web_src/js/features/repo-diff.ts b/web_src/js/features/repo-diff.ts index 58e0d880922dd..06adc9092f487 100644 --- a/web_src/js/features/repo-diff.ts +++ b/web_src/js/features/repo-diff.ts @@ -19,6 +19,7 @@ import { import {POST, GET} from '../modules/fetch.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; import {createTippy} from '../modules/tippy.ts'; +import {initGlobalDropdown} from './common-page.ts'; const {pageData, i18n} = window.config; @@ -100,6 +101,13 @@ function initRepoDiffConversationForm() { } }); + $(document).on('click', '.pull-request-diff-comments', async (e) => { + e.preventDefault(); + initGlobalDropdown(); + // post initiation cleaning up the buttons and scripts + $('.pull-request-diff-comments').remove(); + }); + $(document).on('click', '.resolve-conversation', async function (e) { e.preventDefault(); const comment_id = $(this).data('comment-id');