| @@ -9,6 +9,7 @@ import ( | |||||
| "fmt" | "fmt" | ||||
| "path" | "path" | ||||
| "regexp" | "regexp" | ||||
| "strconv" | |||||
| "strings" | "strings" | ||||
| "time" | "time" | ||||
| "unicode" | "unicode" | ||||
| @@ -77,6 +78,9 @@ type Action struct { | |||||
| ActUser *User `xorm:"-"` | ActUser *User `xorm:"-"` | ||||
| RepoID int64 `xorm:"INDEX"` | RepoID int64 `xorm:"INDEX"` | ||||
| Repo *Repository `xorm:"-"` | Repo *Repository `xorm:"-"` | ||||
| CommentID int64 `xorm:"INDEX"` | |||||
| Comment *Comment `xorm:"-"` | |||||
| IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"` | |||||
| RefName string | RefName string | ||||
| IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` | IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` | ||||
| Content string `xorm:"TEXT"` | Content string `xorm:"TEXT"` | ||||
| @@ -191,6 +195,35 @@ func (a *Action) GetRepoLink() string { | |||||
| return "/" + a.GetRepoPath() | return "/" + a.GetRepoPath() | ||||
| } | } | ||||
| // GetCommentLink returns link to action comment. | |||||
| func (a *Action) GetCommentLink() string { | |||||
| if a == nil { | |||||
| return "#" | |||||
| } | |||||
| if a.Comment == nil && a.CommentID != 0 { | |||||
| a.Comment, _ = GetCommentByID(a.CommentID) | |||||
| } | |||||
| if a.Comment != nil { | |||||
| return a.Comment.HTMLURL() | |||||
| } | |||||
| if len(a.GetIssueInfos()) == 0 { | |||||
| return "#" | |||||
| } | |||||
| //Return link to issue | |||||
| issueIDString := a.GetIssueInfos()[0] | |||||
| issueID, err := strconv.ParseInt(issueIDString, 10, 64) | |||||
| if err != nil { | |||||
| return "#" | |||||
| } | |||||
| issue, err := GetIssueByID(issueID) | |||||
| if err != nil { | |||||
| return "#" | |||||
| } | |||||
| return issue.HTMLURL() | |||||
| } | |||||
| // GetBranch returns the action's repository branch. | // GetBranch returns the action's repository branch. | ||||
| func (a *Action) GetBranch() string { | func (a *Action) GetBranch() string { | ||||
| return a.RefName | return a.RefName | ||||
| @@ -678,6 +711,7 @@ type GetFeedsOptions struct { | |||||
| RequestingUserID int64 | RequestingUserID int64 | ||||
| IncludePrivate bool // include private actions | IncludePrivate bool // include private actions | ||||
| OnlyPerformedBy bool // only actions performed by requested user | OnlyPerformedBy bool // only actions performed by requested user | ||||
| IncludeDeleted bool // include deleted actions | |||||
| } | } | ||||
| // GetFeeds returns actions according to the provided options | // GetFeeds returns actions according to the provided options | ||||
| @@ -706,5 +740,11 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) { | |||||
| if opts.RequestedUser.IsOrganization() { | if opts.RequestedUser.IsOrganization() { | ||||
| sess.In("repo_id", repoIDs) | sess.In("repo_id", repoIDs) | ||||
| } | } | ||||
| if !opts.IncludeDeleted { | |||||
| sess.And("is_deleted = ?", false) | |||||
| } | |||||
| return actions, sess.Find(&actions) | return actions, sess.Find(&actions) | ||||
| } | } | ||||
| @@ -310,6 +310,7 @@ func TestGetFeeds(t *testing.T) { | |||||
| RequestingUserID: user.ID, | RequestingUserID: user.ID, | ||||
| IncludePrivate: true, | IncludePrivate: true, | ||||
| OnlyPerformedBy: false, | OnlyPerformedBy: false, | ||||
| IncludeDeleted: true, | |||||
| }) | }) | ||||
| assert.NoError(t, err) | assert.NoError(t, err) | ||||
| assert.Len(t, actions, 1) | assert.Len(t, actions, 1) | ||||
| @@ -337,6 +338,7 @@ func TestGetFeeds2(t *testing.T) { | |||||
| RequestingUserID: userID, | RequestingUserID: userID, | ||||
| IncludePrivate: true, | IncludePrivate: true, | ||||
| OnlyPerformedBy: false, | OnlyPerformedBy: false, | ||||
| IncludeDeleted: true, | |||||
| }) | }) | ||||
| assert.NoError(t, err) | assert.NoError(t, err) | ||||
| assert.Len(t, actions, 1) | assert.Len(t, actions, 1) | ||||
| @@ -348,6 +350,7 @@ func TestGetFeeds2(t *testing.T) { | |||||
| RequestingUserID: userID, | RequestingUserID: userID, | ||||
| IncludePrivate: false, | IncludePrivate: false, | ||||
| OnlyPerformedBy: false, | OnlyPerformedBy: false, | ||||
| IncludeDeleted: true, | |||||
| }) | }) | ||||
| assert.NoError(t, err) | assert.NoError(t, err) | ||||
| assert.Len(t, actions, 0) | assert.Len(t, actions, 0) | ||||
| @@ -334,6 +334,8 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err | |||||
| Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]), | Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]), | ||||
| RepoID: opts.Repo.ID, | RepoID: opts.Repo.ID, | ||||
| Repo: opts.Repo, | Repo: opts.Repo, | ||||
| Comment: comment, | |||||
| CommentID: comment.ID, | |||||
| IsPrivate: opts.Repo.IsPrivate, | IsPrivate: opts.Repo.IsPrivate, | ||||
| } | } | ||||
| @@ -666,6 +668,7 @@ func DeleteComment(comment *Comment) error { | |||||
| return err | return err | ||||
| } | } | ||||
| } | } | ||||
| sess.Where("comment_id = ?", comment.ID).Cols("is_deleted").Update(&Action{IsDeleted: true}) | |||||
| return sess.Commit() | return sess.Commit() | ||||
| } | } | ||||
| @@ -118,6 +118,8 @@ var migrations = []Migration{ | |||||
| NewMigration("remove columns from action", removeActionColumns), | NewMigration("remove columns from action", removeActionColumns), | ||||
| // v34 -> v35 | // v34 -> v35 | ||||
| NewMigration("give all units to owner teams", giveAllUnitsToOwnerTeams), | NewMigration("give all units to owner teams", giveAllUnitsToOwnerTeams), | ||||
| // v35 -> v36 | |||||
| NewMigration("adds comment to an action", addCommentIDToAction), | |||||
| } | } | ||||
| // Migrate database to current version | // Migrate database to current version | ||||
| @@ -0,0 +1,25 @@ | |||||
| // Copyright 2017 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" | |||||
| "github.com/go-xorm/xorm" | |||||
| ) | |||||
| func addCommentIDToAction(x *xorm.Engine) error { | |||||
| // Action see models/action.go | |||||
| type Action struct { | |||||
| CommentID int64 `xorm:"INDEX"` | |||||
| IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"` | |||||
| } | |||||
| if err := x.Sync2(new(Action)); err != nil { | |||||
| return fmt.Errorf("Sync2: %v", err) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| @@ -54,7 +54,7 @@ func getDashboardContextUser(ctx *context.Context) *models.User { | |||||
| } | } | ||||
| // retrieveFeeds loads feeds for the specified user | // retrieveFeeds loads feeds for the specified user | ||||
| func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isProfile bool) { | |||||
| func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isProfile bool, includeDeletedComments bool) { | |||||
| var requestingID int64 | var requestingID int64 | ||||
| if ctx.User != nil { | if ctx.User != nil { | ||||
| requestingID = ctx.User.ID | requestingID = ctx.User.ID | ||||
| @@ -64,6 +64,7 @@ func retrieveFeeds(ctx *context.Context, user *models.User, includePrivate, isPr | |||||
| RequestingUserID: requestingID, | RequestingUserID: requestingID, | ||||
| IncludePrivate: includePrivate, | IncludePrivate: includePrivate, | ||||
| OnlyPerformedBy: isProfile, | OnlyPerformedBy: isProfile, | ||||
| IncludeDeleted: includeDeletedComments, | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.Handle(500, "GetFeeds", err) | ctx.Handle(500, "GetFeeds", err) | ||||
| @@ -186,7 +187,7 @@ func Dashboard(ctx *context.Context) { | |||||
| ctx.Data["MirrorCount"] = len(mirrors) | ctx.Data["MirrorCount"] = len(mirrors) | ||||
| ctx.Data["Mirrors"] = mirrors | ctx.Data["Mirrors"] = mirrors | ||||
| retrieveFeeds(ctx, ctxUser, true, false) | |||||
| retrieveFeeds(ctx, ctxUser, true, false, false) | |||||
| if ctx.Written() { | if ctx.Written() { | ||||
| return | return | ||||
| } | } | ||||
| @@ -138,7 +138,7 @@ func Profile(ctx *context.Context) { | |||||
| ctx.Data["Keyword"] = keyword | ctx.Data["Keyword"] = keyword | ||||
| switch tab { | switch tab { | ||||
| case "activity": | case "activity": | ||||
| retrieveFeeds(ctx, ctxUser, showPrivate, true) | |||||
| retrieveFeeds(ctx, ctxUser, showPrivate, true, false) | |||||
| if ctx.Written() { | if ctx.Written() { | ||||
| return | return | ||||
| } | } | ||||
| @@ -63,7 +63,7 @@ | |||||
| {{else if eq .GetOpType 7}} | {{else if eq .GetOpType 7}} | ||||
| <span class="text truncate issue title has-emoji">{{index .GetIssueInfos 1}}</span> | <span class="text truncate issue title has-emoji">{{index .GetIssueInfos 1}}</span> | ||||
| {{else if eq .GetOpType 10}} | {{else if eq .GetOpType 10}} | ||||
| <span class="text truncate issue title has-emoji">{{.GetIssueTitle}}</span> | |||||
| <a href="{{.GetCommentLink}}" class="text truncate issue title has-emoji">{{.GetIssueTitle}}</a> | |||||
| <p class="text light grey has-emoji">{{index .GetIssueInfos 1}}</p> | <p class="text light grey has-emoji">{{index .GetIssueInfos 1}}</p> | ||||
| {{else if eq .GetOpType 11}} | {{else if eq .GetOpType 11}} | ||||
| <p class="text light grey has-emoji">{{index .GetIssueInfos 1}}</p> | <p class="text light grey has-emoji">{{index .GetIssueInfos 1}}</p> | ||||