diff --git a/models/issues/comment.go b/models/issues/comment.go index 8e06838f73a32..e4af432f21cda 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -213,41 +213,45 @@ func (r RoleInRepo) LocaleHelper(lang translation.Locale) string { // Comment represents a comment in commit and issue page. type Comment struct { - ID int64 `xorm:"pk autoincr"` - Type CommentType `xorm:"INDEX"` - PosterID int64 `xorm:"INDEX"` - Poster *user_model.User `xorm:"-"` - OriginalAuthor string - OriginalAuthorID int64 - IssueID int64 `xorm:"INDEX"` - Issue *Issue `xorm:"-"` - LabelID int64 - Label *Label `xorm:"-"` - AddedLabels []*Label `xorm:"-"` - RemovedLabels []*Label `xorm:"-"` - OldProjectID int64 - ProjectID int64 - OldProject *project_model.Project `xorm:"-"` - Project *project_model.Project `xorm:"-"` - OldMilestoneID int64 - MilestoneID int64 - OldMilestone *Milestone `xorm:"-"` - Milestone *Milestone `xorm:"-"` - TimeID int64 - Time *TrackedTime `xorm:"-"` - AssigneeID int64 - RemovedAssignee bool - Assignee *user_model.User `xorm:"-"` - AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"` - AssigneeTeam *organization.Team `xorm:"-"` - ResolveDoerID int64 - ResolveDoer *user_model.User `xorm:"-"` - OldTitle string - NewTitle string - OldRef string - NewRef string - DependentIssueID int64 `xorm:"index"` // This is used by issue_service.deleteIssue - DependentIssue *Issue `xorm:"-"` + ID int64 `xorm:"pk autoincr"` + Type CommentType `xorm:"INDEX"` + PosterID int64 `xorm:"INDEX"` + Poster *user_model.User `xorm:"-"` + OriginalAuthor string + OriginalAuthorID int64 + IssueID int64 `xorm:"INDEX"` + Issue *Issue `xorm:"-"` + LabelID int64 + Label *Label `xorm:"-"` + AddedLabels []*Label `xorm:"-"` + RemovedLabels []*Label `xorm:"-"` + OldProjectID int64 + ProjectID int64 + OldProject *project_model.Project `xorm:"-"` + Project *project_model.Project `xorm:"-"` + OldProjectBoardID int64 + ProjectBoardID int64 + OldProjectBoard *project_model.Board `xorm:"-"` + ProjectBoard *project_model.Board `xorm:"-"` + OldMilestoneID int64 + MilestoneID int64 + OldMilestone *Milestone `xorm:"-"` + Milestone *Milestone `xorm:"-"` + TimeID int64 + Time *TrackedTime `xorm:"-"` + AssigneeID int64 + RemovedAssignee bool + Assignee *user_model.User `xorm:"-"` + AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"` + AssigneeTeam *organization.Team `xorm:"-"` + ResolveDoerID int64 + ResolveDoer *user_model.User `xorm:"-"` + OldTitle string + NewTitle string + OldRef string + NewRef string + DependentIssueID int64 `xorm:"index"` // This is used by issue_service.deleteIssue + DependentIssue *Issue `xorm:"-"` CommitID int64 Line int64 // - previous line / + proposed line @@ -530,6 +534,31 @@ func (c *Comment) LoadProject(ctx context.Context) error { return nil } +// LoadBoard if comment.Type is CommentTypeProjectBoard, then load project. +func (c *Comment) LoadProjectBoard(ctx context.Context) error { + if c.OldProjectBoardID > 0 { + var oldProjectBoard project_model.Board + has, err := db.GetEngine(ctx).ID(c.OldProjectBoardID).Get(&oldProjectBoard) + if err != nil { + return err + } else if has { + c.OldProjectBoard = &oldProjectBoard + } + } + + if c.ProjectBoardID > 0 { + var projectBoard project_model.Board + has, err := db.GetEngine(ctx).ID(c.ProjectBoardID).Get(&projectBoard) + if err != nil { + return err + } else if has { + c.ProjectBoard = &projectBoard + } + } + + return nil +} + // LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone func (c *Comment) LoadMilestone(ctx context.Context) error { if c.OldMilestoneID > 0 { @@ -784,38 +813,40 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment, } comment := &Comment{ - Type: opts.Type, - PosterID: opts.Doer.ID, - Poster: opts.Doer, - IssueID: opts.Issue.ID, - LabelID: LabelID, - OldMilestoneID: opts.OldMilestoneID, - MilestoneID: opts.MilestoneID, - OldProjectID: opts.OldProjectID, - ProjectID: opts.ProjectID, - TimeID: opts.TimeID, - RemovedAssignee: opts.RemovedAssignee, - AssigneeID: opts.AssigneeID, - AssigneeTeamID: opts.AssigneeTeamID, - CommitID: opts.CommitID, - CommitSHA: opts.CommitSHA, - Line: opts.LineNum, - Content: opts.Content, - OldTitle: opts.OldTitle, - NewTitle: opts.NewTitle, - OldRef: opts.OldRef, - NewRef: opts.NewRef, - DependentIssueID: opts.DependentIssueID, - TreePath: opts.TreePath, - ReviewID: opts.ReviewID, - Patch: opts.Patch, - RefRepoID: opts.RefRepoID, - RefIssueID: opts.RefIssueID, - RefCommentID: opts.RefCommentID, - RefAction: opts.RefAction, - RefIsPull: opts.RefIsPull, - IsForcePush: opts.IsForcePush, - Invalidated: opts.Invalidated, + Type: opts.Type, + PosterID: opts.Doer.ID, + Poster: opts.Doer, + IssueID: opts.Issue.ID, + LabelID: LabelID, + OldMilestoneID: opts.OldMilestoneID, + MilestoneID: opts.MilestoneID, + OldProjectID: opts.OldProjectID, + ProjectID: opts.ProjectID, + OldProjectBoardID: opts.OldProjectBoardID, + ProjectBoardID: opts.ProjectBoardID, + TimeID: opts.TimeID, + RemovedAssignee: opts.RemovedAssignee, + AssigneeID: opts.AssigneeID, + AssigneeTeamID: opts.AssigneeTeamID, + CommitID: opts.CommitID, + CommitSHA: opts.CommitSHA, + Line: opts.LineNum, + Content: opts.Content, + OldTitle: opts.OldTitle, + NewTitle: opts.NewTitle, + OldRef: opts.OldRef, + NewRef: opts.NewRef, + DependentIssueID: opts.DependentIssueID, + TreePath: opts.TreePath, + ReviewID: opts.ReviewID, + Patch: opts.Patch, + RefRepoID: opts.RefRepoID, + RefIssueID: opts.RefIssueID, + RefCommentID: opts.RefCommentID, + RefAction: opts.RefAction, + RefIsPull: opts.RefIsPull, + IsForcePush: opts.IsForcePush, + Invalidated: opts.Invalidated, } if _, err = e.Insert(comment); err != nil { return nil, err @@ -961,34 +992,36 @@ type CreateCommentOptions struct { Issue *Issue Label *Label - DependentIssueID int64 - OldMilestoneID int64 - MilestoneID int64 - OldProjectID int64 - ProjectID int64 - TimeID int64 - AssigneeID int64 - AssigneeTeamID int64 - RemovedAssignee bool - OldTitle string - NewTitle string - OldRef string - NewRef string - CommitID int64 - CommitSHA string - Patch string - LineNum int64 - TreePath string - ReviewID int64 - Content string - Attachments []string // UUIDs of attachments - RefRepoID int64 - RefIssueID int64 - RefCommentID int64 - RefAction references.XRefAction - RefIsPull bool - IsForcePush bool - Invalidated bool + DependentIssueID int64 + OldMilestoneID int64 + MilestoneID int64 + OldProjectID int64 + ProjectID int64 + OldProjectBoardID int64 + ProjectBoardID int64 + TimeID int64 + AssigneeID int64 + AssigneeTeamID int64 + RemovedAssignee bool + OldTitle string + NewTitle string + OldRef string + NewRef string + CommitID int64 + CommitSHA string + Patch string + LineNum int64 + TreePath string + ReviewID int64 + Content string + Attachments []string // UUIDs of attachments + RefRepoID int64 + RefIssueID int64 + RefCommentID int64 + RefAction references.XRefAction + RefIsPull bool + IsForcePush bool + Invalidated bool } // GetCommentByID returns the comment by given ID. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index a7a7a4f4c50f9..720f487e79508 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1391,12 +1391,16 @@ issues.remove_labels = removed the %s labels %s issues.add_remove_labels = added %s and removed %s labels %s issues.add_milestone_at = `added this to the %s milestone %s` issues.add_project_at = `added this to the %s project %s` +issues.add_project_board_at = `added this to %s %s` issues.change_milestone_at = `modified the milestone from %s to %s %s` issues.change_project_at = `modified the project from %s to %s %s` +issues.change_project_board_at = `moved this from %s to %s %s` issues.remove_milestone_at = `removed this from the %s milestone %s` issues.remove_project_at = `removed this from the %s project %s` +issues.remove_project_board_at = `removed the status %s` issues.deleted_milestone = `(deleted)` issues.deleted_project = `(deleted)` +issues.deleted_project_board = `(deleted)` issues.self_assign_at = `self-assigned this %s` issues.add_assignee_at = `was assigned by %s %s` issues.remove_assignee_at = `was unassigned by %s %s` diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 439fdf644bb6a..29faf244b690f 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -694,7 +694,9 @@ func MoveIssues(ctx *context.Context) { } type movedIssuesForm struct { - Issues []struct { + IssueID int64 `json:"issueID"` + From int64 `json:"from"` + Issues []struct { IssueID int64 `json:"issueID"` Sorting int64 `json:"sorting"` } `json:"issues"` @@ -721,6 +723,16 @@ func MoveIssues(ctx *context.Context) { return } + issue, err := issues_model.GetIssueByID(ctx, form.IssueID) + if err != nil { + if issues_model.IsErrIssueNotExist(err) { + ctx.NotFound("IssueNotExisting", nil) + } else { + ctx.ServerError("GetIssueByID", err) + } + return + } + if len(movedIssues) != len(form.Issues) { ctx.ServerError("some issues do not exist", errors.New("some issues do not exist")) return @@ -738,6 +750,27 @@ func MoveIssues(ctx *context.Context) { } } + if form.From > 0 || board.ID > 0 { + if err := issue.LoadRepo(ctx); err != nil { + ctx.ServerError("LoadRepo", err) + return + } + + if form.From != board.ID { + if _, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{ + Type: issues_model.CommentTypeProjectBoard, + Doer: ctx.Doer, + Repo: issue.Repo, + Issue: issue, + OldProjectBoardID: form.From, + ProjectBoardID: board.ID, + }); err != nil { + ctx.ServerError("CreateComment", err) + return + } + } + } + if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil { ctx.ServerError("MoveIssuesOnProjectBoard", err) return diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 94300da868330..8cfe07707dabd 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -1664,7 +1664,24 @@ func ViewIssue(ctx *context.Context) { if comment.ProjectID > 0 && comment.Project == nil { comment.Project = ghostProject } + } else if comment.Type == issues_model.CommentTypeProjectBoard { + if err = comment.LoadProjectBoard(ctx); err != nil { + ctx.ServerError("LoadProjectBoard", err) + return + } + + ghostProjectBoard := &project_model.Board{ + ID: -1, + Title: ctx.Tr("repo.issues.deleted_project_board"), + } + + if comment.OldProjectBoardID > 0 && comment.OldProjectBoard == nil { + comment.OldProjectBoard = ghostProjectBoard + } + if comment.ProjectBoardID > 0 && comment.ProjectBoard == nil { + comment.ProjectBoard = ghostProjectBoard + } } else if comment.Type == issues_model.CommentTypeAssignees || comment.Type == issues_model.CommentTypeReviewRequest { if err = comment.LoadAssigneeUserAndTeam(ctx); err != nil { ctx.ServerError("LoadAssigneeUserAndTeam", err) diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 6417024f8ba3c..b00d2fbfeb2b8 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -644,7 +644,9 @@ func MoveIssues(ctx *context.Context) { } type movedIssuesForm struct { - Issues []struct { + IssueID int64 `json:"issueID"` + From int64 `json:"from"` + Issues []struct { IssueID int64 `json:"issueID"` Sorting int64 `json:"sorting"` } `json:"issues"` @@ -671,6 +673,16 @@ func MoveIssues(ctx *context.Context) { return } + issue, err := issues_model.GetIssueByID(ctx, form.IssueID) + if err != nil { + if issues_model.IsErrIssueNotExist(err) { + ctx.NotFound("IssueNotExisting", nil) + } else { + ctx.ServerError("GetIssueByID", err) + } + return + } + if len(movedIssues) != len(form.Issues) { ctx.ServerError("some issues do not exist", errors.New("some issues do not exist")) return @@ -683,6 +695,27 @@ func MoveIssues(ctx *context.Context) { } } + if form.From > 0 || board.ID > 0 { + if err := issue.LoadRepo(ctx); err != nil { + ctx.ServerError("LoadRepo", err) + return + } + + if form.From != board.ID { + if _, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{ + Type: issues_model.CommentTypeProjectBoard, + Doer: ctx.Doer, + Repo: issue.Repo, + Issue: issue, + OldProjectBoardID: form.From, + ProjectBoardID: board.ID, + }); err != nil { + ctx.ServerError("CreateComment", err) + return + } + } + } + if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil { ctx.ServerError("MoveIssuesOnProjectBoard", err) return diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 817f20af203ec..073944ff92ff7 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -732,6 +732,21 @@ {{end}} + {{else if eq .Type 31}} +
+ {{svg "octicon-project"}} + {{template "shared/user/avatarlink" dict "user" .Poster}} + + {{template "shared/user/authorlink" .Poster}} + {{if and (gt .OldProjectBoardID 0) (gt .ProjectBoardID 0)}} + {{ctx.Locale.Tr "repo.issues.change_project_board_at" (.OldProjectBoard.Title | Escape) (.ProjectBoard.Title | Escape) $createdStr | Safe}} + {{else if gt .OldProjectBoardID 0}} + {{ctx.Locale.Tr "repo.issues.remove_project_board_at" $createdStr | Safe}} + {{else if gt .ProjectBoardID 0}} + {{ctx.Locale.Tr "repo.issues.add_project_board_at" (.ProjectBoard.Title | Escape) $createdStr | Safe}} + {{end}} + +
{{else if eq .Type 32}}
diff --git a/web_src/js/features/repo-projects.js b/web_src/js/features/repo-projects.js index 5a2a7e72ef335..c9290aad80f86 100644 --- a/web_src/js/features/repo-projects.js +++ b/web_src/js/features/repo-projects.js @@ -32,6 +32,8 @@ function moveIssue({item, from, to, oldIndex}) { updateIssueCount(to); const columnSorting = { + issueID: parseInt(item.dataset.issue), + from: parseInt(from.dataset.board), issues: Array.from(columnCards, (card, i) => ({ issueID: parseInt($(card).attr('data-issue')), sorting: i,