mirror of https://github.com/go-gitea/gitea
Compare commits
4 Commits
19c496655d
...
a3443b577d
Author | SHA1 | Date |
---|---|---|
a1012112796 | a3443b577d | |
GiteaBot | 27861d711b | |
silverwind | c93eefb42b | |
a1012112796 | 04bae31b49 |
|
@ -8,7 +8,7 @@
|
||||||
id: 2
|
id: 2
|
||||||
issue_id: 2
|
issue_id: 2
|
||||||
project_id: 1
|
project_id: 1
|
||||||
project_board_id: 0 # no board assigned
|
project_board_id: 5
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 3
|
id: 3
|
||||||
|
|
|
@ -220,6 +220,13 @@ func (r RoleInRepo) LocaleHelper(lang translation.Locale) string {
|
||||||
return lang.TrString("repo.issues.role." + string(r) + "_helper")
|
return lang.TrString("repo.issues.role." + string(r) + "_helper")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommentProjectBoardExtendData extend data of CommentTypeProjectBoard,
|
||||||
|
// will be store in `Comment.Content` as json format
|
||||||
|
type CommentProjectBoardExtendData struct {
|
||||||
|
FromBoardTitle string
|
||||||
|
ToBoardTitle string
|
||||||
|
}
|
||||||
|
|
||||||
// Comment represents a comment in commit and issue page.
|
// Comment represents a comment in commit and issue page.
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
@ -301,6 +308,8 @@ type Comment struct {
|
||||||
NewCommit string `xorm:"-"`
|
NewCommit string `xorm:"-"`
|
||||||
CommitsNum int64 `xorm:"-"`
|
CommitsNum int64 `xorm:"-"`
|
||||||
IsForcePush bool `xorm:"-"`
|
IsForcePush bool `xorm:"-"`
|
||||||
|
|
||||||
|
ProjectBoard *CommentProjectBoardExtendData `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -539,6 +548,15 @@ func (c *Comment) LoadProject(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Comment) LoadProjectBoard() error {
|
||||||
|
if c.Type != CommentTypeProjectBoard || c.ProjectBoard != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ProjectBoard = &CommentProjectBoardExtendData{}
|
||||||
|
return json.Unmarshal([]byte(c.Content), c.ProjectBoard)
|
||||||
|
}
|
||||||
|
|
||||||
// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
|
// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
|
||||||
func (c *Comment) LoadMilestone(ctx context.Context) error {
|
func (c *Comment) LoadMilestone(ctx context.Context) error {
|
||||||
if c.OldMilestoneID > 0 {
|
if c.OldMilestoneID > 0 {
|
||||||
|
@ -828,6 +846,15 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment,
|
||||||
IsForcePush: opts.IsForcePush,
|
IsForcePush: opts.IsForcePush,
|
||||||
Invalidated: opts.Invalidated,
|
Invalidated: opts.Invalidated,
|
||||||
}
|
}
|
||||||
|
if comment.Type == CommentTypeProjectBoard {
|
||||||
|
extDataJSON, err := json.Marshal(opts.ProjectBoard)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
comment.Content = string(extDataJSON)
|
||||||
|
comment.ProjectBoard = opts.ProjectBoard
|
||||||
|
}
|
||||||
|
|
||||||
if _, err = e.Insert(comment); err != nil {
|
if _, err = e.Insert(comment); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1007,6 +1034,8 @@ type CreateCommentOptions struct {
|
||||||
RefIsPull bool
|
RefIsPull bool
|
||||||
IsForcePush bool
|
IsForcePush bool
|
||||||
Invalidated bool
|
Invalidated bool
|
||||||
|
|
||||||
|
ProjectBoard *CommentProjectBoardExtendData
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommentByID returns the comment by given ID.
|
// GetCommentByID returns the comment by given ID.
|
||||||
|
|
|
@ -140,6 +140,8 @@ type Issue struct {
|
||||||
|
|
||||||
// For view issue page.
|
// For view issue page.
|
||||||
ShowRole RoleDescriptor `xorm:"-"`
|
ShowRole RoleDescriptor `xorm:"-"`
|
||||||
|
|
||||||
|
ProjectIssue *project_model.ProjectIssue `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -315,6 +317,10 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = issue.LoadProjectIssue(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err = issue.LoadAssignees(ctx); err != nil {
|
if err = issue.LoadAssignees(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,14 +226,15 @@ func (issues IssueList) loadMilestones(ctx context.Context) error {
|
||||||
|
|
||||||
func (issues IssueList) LoadProjects(ctx context.Context) error {
|
func (issues IssueList) LoadProjects(ctx context.Context) error {
|
||||||
issueIDs := issues.getIssueIDs()
|
issueIDs := issues.getIssueIDs()
|
||||||
projectMaps := make(map[int64]*project_model.Project, len(issues))
|
|
||||||
left := len(issueIDs)
|
left := len(issueIDs)
|
||||||
|
|
||||||
type projectWithIssueID struct {
|
type projectWithIssueID struct {
|
||||||
*project_model.Project `xorm:"extends"`
|
*project_model.Project `xorm:"extends"`
|
||||||
IssueID int64
|
ProjectIssue *project_model.ProjectIssue `xorm:"extends"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
projectMaps := make(map[int64]*projectWithIssueID, len(issues))
|
||||||
|
|
||||||
for left > 0 {
|
for left > 0 {
|
||||||
limit := db.DefaultMaxInSize
|
limit := db.DefaultMaxInSize
|
||||||
if left < limit {
|
if left < limit {
|
||||||
|
@ -243,7 +244,7 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
|
||||||
projects := make([]*projectWithIssueID, 0, limit)
|
projects := make([]*projectWithIssueID, 0, limit)
|
||||||
err := db.GetEngine(ctx).
|
err := db.GetEngine(ctx).
|
||||||
Table("project").
|
Table("project").
|
||||||
Select("project.*, project_issue.issue_id").
|
Select("project.*, project_issue.*").
|
||||||
Join("INNER", "project_issue", "project.id = project_issue.project_id").
|
Join("INNER", "project_issue", "project.id = project_issue.project_id").
|
||||||
In("project_issue.issue_id", issueIDs[:limit]).
|
In("project_issue.issue_id", issueIDs[:limit]).
|
||||||
Find(&projects)
|
Find(&projects)
|
||||||
|
@ -251,14 +252,20 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, project := range projects {
|
for _, project := range projects {
|
||||||
projectMaps[project.IssueID] = project.Project
|
projectMaps[project.ProjectIssue.IssueID] = project
|
||||||
}
|
}
|
||||||
left -= limit
|
left -= limit
|
||||||
issueIDs = issueIDs[limit:]
|
issueIDs = issueIDs[limit:]
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
issue.Project = projectMaps[issue.ID]
|
item, exist := projectMaps[issue.ID]
|
||||||
|
if !exist {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
issue.Project = item.Project
|
||||||
|
issue.ProjectIssue = item.ProjectIssue
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -554,6 +561,10 @@ func (issues IssueList) LoadAttributes(ctx context.Context) error {
|
||||||
return fmt.Errorf("issue.loadAttributes: loadProjects: %w", err)
|
return fmt.Errorf("issue.loadAttributes: loadProjects: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := issues.LoadProjectIssueBoards(ctx); err != nil {
|
||||||
|
return fmt.Errorf("issue.loadAttributes: LoadProjectIssueBoards: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := issues.loadAssignees(ctx); err != nil {
|
if err := issues.loadAssignees(ctx); err != nil {
|
||||||
return fmt.Errorf("issue.loadAttributes: loadAssignees: %w", err)
|
return fmt.Errorf("issue.loadAttributes: loadAssignees: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -626,3 +637,60 @@ func (issues IssueList) LoadIsRead(ctx context.Context, userID int64) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (issues IssueList) getProjectIssueBoardIDs() []int64 {
|
||||||
|
boardIDmap := make(map[int64]bool, 5)
|
||||||
|
|
||||||
|
for _, issue := range issues {
|
||||||
|
if issue.ProjectIssue != nil {
|
||||||
|
boardIDmap[issue.ProjectIssue.ProjectBoardID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bordIDs := make([]int64, 0, len(boardIDmap))
|
||||||
|
for id := range boardIDmap {
|
||||||
|
bordIDs = append(bordIDs, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bordIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (issues IssueList) LoadProjectIssueBoards(ctx context.Context) error {
|
||||||
|
boardIDs := issues.getProjectIssueBoardIDs()
|
||||||
|
if len(boardIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
boardMaps := make(map[int64]*project_model.Board, len(boardIDs))
|
||||||
|
left := len(boardIDs)
|
||||||
|
for left > 0 {
|
||||||
|
limit := db.DefaultMaxInSize
|
||||||
|
if left < limit {
|
||||||
|
limit = left
|
||||||
|
}
|
||||||
|
err := db.GetEngine(ctx).
|
||||||
|
In("id", boardIDs[:limit]).
|
||||||
|
Find(&boardMaps)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
left -= limit
|
||||||
|
boardIDs = boardIDs[limit:]
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, issue := range issues {
|
||||||
|
if issue.ProjectIssue != nil {
|
||||||
|
board, exist := boardMaps[issue.ProjectIssue.ProjectBoardID]
|
||||||
|
if exist {
|
||||||
|
issue.ProjectIssue.ProjectBoard = board
|
||||||
|
} else {
|
||||||
|
issue.ProjectIssue.ProjectBoard = &project_model.Board{
|
||||||
|
ID: -1,
|
||||||
|
Title: "Deleted",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -68,6 +68,10 @@ func TestIssueList_LoadAttributes(t *testing.T) {
|
||||||
assert.Equal(t, int64(400), issue.TotalTrackedTime)
|
assert.Equal(t, int64(400), issue.TotalTrackedTime)
|
||||||
assert.NotNil(t, issue.Project)
|
assert.NotNil(t, issue.Project)
|
||||||
assert.Equal(t, int64(1), issue.Project.ID)
|
assert.Equal(t, int64(1), issue.Project.ID)
|
||||||
|
assert.NotNil(t, issue.ProjectIssue)
|
||||||
|
assert.Equal(t, int64(1), issue.ProjectIssue.IssueID)
|
||||||
|
assert.NotNil(t, issue.ProjectIssue.ProjectBoard)
|
||||||
|
assert.Equal(t, int64(1), issue.ProjectIssue.ProjectBoard.ID)
|
||||||
} else {
|
} else {
|
||||||
assert.Nil(t, issue.Project)
|
assert.Nil(t, issue.Project)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,23 @@ func (issue *Issue) LoadProject(ctx context.Context) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (issue *Issue) LoadProjectIssue(ctx context.Context) (err error) {
|
||||||
|
if issue.Project == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.ProjectIssue != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
issue.ProjectIssue, err = project_model.GetProjectIssueByIssueID(ctx, issue.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return issue.ProjectIssue.LoadProjectBoard(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (issue *Issue) projectID(ctx context.Context) int64 {
|
func (issue *Issue) projectID(ctx context.Context) int64 {
|
||||||
var ip project_model.ProjectIssue
|
var ip project_model.ProjectIssue
|
||||||
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
|
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
|
||||||
|
@ -107,6 +124,7 @@ func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.Use
|
||||||
|
|
||||||
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
|
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
|
||||||
oldProjectID := issue.projectID(ctx)
|
oldProjectID := issue.projectID(ctx)
|
||||||
|
newBoardID := int64(0)
|
||||||
|
|
||||||
if err := issue.LoadRepo(ctx); err != nil {
|
if err := issue.LoadRepo(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -121,6 +139,12 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
|
||||||
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
|
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
|
||||||
return fmt.Errorf("issue's repository is not the same as project's repository")
|
return fmt.Errorf("issue's repository is not the same as project's repository")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newBoard, err := newProject.GetDefaultBoard(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newBoardID = newBoard.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
|
if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
|
||||||
|
@ -141,7 +165,8 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.Insert(ctx, &project_model.ProjectIssue{
|
return db.Insert(ctx, &project_model.ProjectIssue{
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
ProjectID: newProjectID,
|
ProjectID: newProjectID,
|
||||||
|
ProjectBoardID: newBoardID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package issues
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectMovedIssuesFormItem struct {
|
||||||
|
IssueID int64 `json:"issueID"`
|
||||||
|
Sorting int64 `json:"sorting"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectMovedIssuesForm struct {
|
||||||
|
Issues []ProjectMovedIssuesFormItem `json:"issues"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ProjectMovedIssuesForm) ToSortedIssueIDs() (issueIDs, issueSorts []int64) {
|
||||||
|
sort.Slice(p.Issues, func(i, j int) bool { return p.Issues[i].Sorting < p.Issues[j].Sorting })
|
||||||
|
|
||||||
|
issueIDs = make([]int64, 0, len(p.Issues))
|
||||||
|
issueSorts = make([]int64, 0, len(p.Issues))
|
||||||
|
|
||||||
|
for _, issue := range p.Issues {
|
||||||
|
issueIDs = append(issueIDs, issue.IssueID)
|
||||||
|
issueSorts = append(issueSorts, issue.Sorting)
|
||||||
|
}
|
||||||
|
|
||||||
|
return issueIDs, issueSorts
|
||||||
|
}
|
||||||
|
|
||||||
|
func MoveIssuesOnProjectBoard(ctx context.Context, doer *user_model.User, form *ProjectMovedIssuesForm, project *project_model.Project, board *project_model.Board) error {
|
||||||
|
issueIDs, issueSorts := form.ToSortedIssueIDs()
|
||||||
|
|
||||||
|
movedIssues, err := GetIssuesByIDs(ctx, issueIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(movedIssues) != len(form.Issues) {
|
||||||
|
return errors.New("some issues do not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = movedIssues.LoadRepositories(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = movedIssues.LoadProjects(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = movedIssues.LoadProjectIssueBoards(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, issue := range movedIssues {
|
||||||
|
if issue.RepoID != project.RepoID && issue.Repo.OwnerID != project.OwnerID {
|
||||||
|
return errors.New("Some issue's repoID is not equal to project's repoID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, issueIDs, issueSorts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, issue := range movedIssues {
|
||||||
|
if issue.ProjectIssue.ProjectBoardID == board.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = CreateComment(ctx, &CreateCommentOptions{
|
||||||
|
Type: CommentTypeProjectBoard,
|
||||||
|
Doer: doer,
|
||||||
|
Repo: issue.Repo,
|
||||||
|
Issue: issue,
|
||||||
|
ProjectID: project.ID,
|
||||||
|
ProjectBoard: &CommentProjectBoardExtendData{
|
||||||
|
FromBoardTitle: issue.ProjectIssue.ProjectBoard.Title,
|
||||||
|
ToBoardTitle: board.Title,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package issues
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProjectMovedIssuesForm_ToSortedIssueIDs(t *testing.T) {
|
||||||
|
opts := &ProjectMovedIssuesForm{
|
||||||
|
Issues: []ProjectMovedIssuesFormItem{
|
||||||
|
{
|
||||||
|
IssueID: 5,
|
||||||
|
Sorting: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IssueID: 1,
|
||||||
|
Sorting: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IssueID: 6,
|
||||||
|
Sorting: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, sorts := opts.ToSortedIssueIDs()
|
||||||
|
|
||||||
|
assert.EqualValues(t, sorts, []int64{1, 3, 4})
|
||||||
|
assert.EqualValues(t, ids, []int64{5, 6, 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoveIssuesOnProjectBoard(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
project := unittest.AssertExistsAndLoadBean(t, &project_model.Project{ID: 1})
|
||||||
|
toBoard := unittest.AssertExistsAndLoadBean(t, &project_model.Board{ID: 2})
|
||||||
|
|
||||||
|
list, err := LoadIssuesFromBoardList(db.DefaultContext, []*project_model.Board{toBoard})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, len(list[toBoard.ID]))
|
||||||
|
assert.EqualValues(t, 3, list[toBoard.ID][0].ID)
|
||||||
|
|
||||||
|
opts := &ProjectMovedIssuesForm{
|
||||||
|
Issues: []ProjectMovedIssuesFormItem{
|
||||||
|
{
|
||||||
|
IssueID: 1,
|
||||||
|
Sorting: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IssueID: 2,
|
||||||
|
Sorting: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IssueID: 3,
|
||||||
|
Sorting: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.NoError(t, MoveIssuesOnProjectBoard(db.DefaultContext, doer, opts, project, toBoard))
|
||||||
|
|
||||||
|
list, err = LoadIssuesFromBoardList(db.DefaultContext, []*project_model.Board{toBoard})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 3, len(list[toBoard.ID]))
|
||||||
|
|
||||||
|
assert.EqualValues(t, 3, list[toBoard.ID][0].ID)
|
||||||
|
assert.EqualValues(t, 1, list[toBoard.ID][1].ID)
|
||||||
|
assert.EqualValues(t, 2, list[toBoard.ID][2].ID)
|
||||||
|
}
|
|
@ -247,7 +247,7 @@ func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultB, err := p.getDefaultBoard(ctx)
|
defaultB, err := p.GetDefaultBoard(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -255,8 +255,8 @@ func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
||||||
return append([]*Board{defaultB}, boards...), nil
|
return append([]*Board{defaultB}, boards...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDefaultBoard return default board and ensure only one exists
|
// GetDefaultBoard return default board and ensure only one exists
|
||||||
func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) {
|
func (p *Project) GetDefaultBoard(ctx context.Context) (*Board, error) {
|
||||||
var board Board
|
var board Board
|
||||||
has, err := db.GetEngine(ctx).
|
has, err := db.GetEngine(ctx).
|
||||||
Where("project_id=? AND `default` = ?", p.ID, true).
|
Where("project_id=? AND `default` = ?", p.ID, true).
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestGetDefaultBoard(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// check if default board was added
|
// check if default board was added
|
||||||
board, err := projectWithoutDefault.getDefaultBoard(db.DefaultContext)
|
board, err := projectWithoutDefault.GetDefaultBoard(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(5), board.ProjectID)
|
assert.Equal(t, int64(5), board.ProjectID)
|
||||||
assert.Equal(t, "Uncategorized", board.Title)
|
assert.Equal(t, "Uncategorized", board.Title)
|
||||||
|
@ -28,7 +28,7 @@ func TestGetDefaultBoard(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// check if multiple defaults were removed
|
// check if multiple defaults were removed
|
||||||
board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext)
|
board, err = projectWithMultipleDefaults.GetDefaultBoard(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(6), board.ProjectID)
|
assert.Equal(t, int64(6), board.ProjectID)
|
||||||
assert.Equal(t, int64(9), board.ID)
|
assert.Equal(t, int64(9), board.ID)
|
||||||
|
|
|
@ -17,8 +17,8 @@ type ProjectIssue struct { //revive:disable-line:exported
|
||||||
IssueID int64 `xorm:"INDEX"`
|
IssueID int64 `xorm:"INDEX"`
|
||||||
ProjectID int64 `xorm:"INDEX"`
|
ProjectID int64 `xorm:"INDEX"`
|
||||||
|
|
||||||
// If 0, then it has not been added to a specific board in the project
|
ProjectBoardID int64 `xorm:"INDEX"`
|
||||||
ProjectBoardID int64 `xorm:"INDEX"`
|
ProjectBoard *Board `xorm:"-"`
|
||||||
|
|
||||||
// the sorting order on the board
|
// the sorting order on the board
|
||||||
Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
|
Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||||
|
@ -76,33 +76,76 @@ func (p *Project) NumOpenIssues(ctx context.Context) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
|
// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
|
||||||
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error {
|
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, issueIDs, issueSorts []int64) error {
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
sess := db.GetEngine(ctx)
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
|
|
||||||
issueIDs := make([]int64, 0, len(sortedIssueIDs))
|
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
|
||||||
for _, issueID := range sortedIssueIDs {
|
if err != nil {
|
||||||
issueIDs = append(issueIDs, issueID)
|
return err
|
||||||
}
|
}
|
||||||
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
|
if int(count) != len(issueIDs) {
|
||||||
|
return fmt.Errorf("all issues have to be added to a project first")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, issueID := range issueIDs {
|
||||||
|
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, issueSorts[i], issueID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if int(count) != len(sortedIssueIDs) {
|
}
|
||||||
return fmt.Errorf("all issues have to be added to a project first")
|
|
||||||
}
|
|
||||||
|
|
||||||
for sorting, issueID := range sortedIssueIDs {
|
return nil
|
||||||
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Board) removeIssues(ctx context.Context) error {
|
func (b *Board) removeIssues(ctx context.Context) error {
|
||||||
_, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", b.ID)
|
_, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", b.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrProjectIssueNotExist struct {
|
||||||
|
IssueID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrProjectIssueNotExist) Error() string {
|
||||||
|
return fmt.Sprintf("can't find project issue [issue_id: %d]", e.IssueID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsErrProjectIssueNotExist(e error) bool {
|
||||||
|
_, ok := e.(ErrProjectIssueNotExist)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProjectIssueByIssueID(ctx context.Context, issueID int64) (*ProjectIssue, error) {
|
||||||
|
issue := &ProjectIssue{}
|
||||||
|
|
||||||
|
has, err := db.GetEngine(ctx).Where("issue_id = ?", issueID).Get(issue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has {
|
||||||
|
return nil, ErrProjectIssueNotExist{IssueID: issueID}
|
||||||
|
}
|
||||||
|
|
||||||
|
return issue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (issue *ProjectIssue) LoadProjectBoard(ctx context.Context) error {
|
||||||
|
if issue.ProjectBoard != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
issue.ProjectBoard, err = GetBoard(ctx, issue.ProjectBoardID)
|
||||||
|
if IsErrProjectBoardNotExist(err) {
|
||||||
|
issue.ProjectBoard = &Board{
|
||||||
|
ID: -1,
|
||||||
|
Title: "Deleted",
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -373,12 +373,6 @@ func searchIssueInProject(t *testing.T) {
|
||||||
},
|
},
|
||||||
[]int64{1},
|
[]int64{1},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
SearchOptions{
|
|
||||||
ProjectBoardID: optional.Some(int64(0)), // issue with in default board
|
|
||||||
},
|
|
||||||
[]int64{2},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||||
|
|
|
@ -1471,6 +1471,7 @@ issues.add_milestone_at = `added this to the <b>%s</b> milestone %s`
|
||||||
issues.add_project_at = `added this to the <b>%s</b> project %s`
|
issues.add_project_at = `added this to the <b>%s</b> project %s`
|
||||||
issues.change_milestone_at = `modified the milestone from <b>%s</b> to <b>%s</b> %s`
|
issues.change_milestone_at = `modified the milestone from <b>%s</b> to <b>%s</b> %s`
|
||||||
issues.change_project_at = `modified the project from <b>%s</b> to <b>%s</b> %s`
|
issues.change_project_at = `modified the project from <b>%s</b> to <b>%s</b> %s`
|
||||||
|
issues.change_project_board_at = `moved this from <b>%s</b> to <b>%s</b> in <b>%s</b> %s`
|
||||||
issues.remove_milestone_at = `removed this from the <b>%s</b> milestone %s`
|
issues.remove_milestone_at = `removed this from the <b>%s</b> milestone %s`
|
||||||
issues.remove_project_at = `removed this from the <b>%s</b> project %s`
|
issues.remove_project_at = `removed this from the <b>%s</b> project %s`
|
||||||
issues.deleted_milestone = `(deleted)`
|
issues.deleted_milestone = `(deleted)`
|
||||||
|
|
|
@ -436,6 +436,7 @@ oauth_signin_submit=Vincular conta
|
||||||
oauth.signin.error=Ocorreu um erro durante o processamento do pedido de autorização. Se este erro persistir, contacte o administrador.
|
oauth.signin.error=Ocorreu um erro durante o processamento do pedido de autorização. Se este erro persistir, contacte o administrador.
|
||||||
oauth.signin.error.access_denied=O pedido de autorização foi negado.
|
oauth.signin.error.access_denied=O pedido de autorização foi negado.
|
||||||
oauth.signin.error.temporarily_unavailable=A autorização falhou porque o servidor de autenticação está temporariamente indisponĂvel. Tente mais tarde.
|
oauth.signin.error.temporarily_unavailable=A autorização falhou porque o servidor de autenticação está temporariamente indisponĂvel. Tente mais tarde.
|
||||||
|
oauth_callback_unable_auto_reg=O registo automático está habilitado, mas o fornecedor OAuth2 %[1]s sinalizou campos em falta: %[2]s, por isso nĂŁo foi possĂvel criar uma conta automaticamente. Crie ou vincule uma conta ou contacte o administrador do sĂtio.
|
||||||
openid_connect_submit=Estabelecer ligação
|
openid_connect_submit=Estabelecer ligação
|
||||||
openid_connect_title=Estabelecer ligação a uma conta existente
|
openid_connect_title=Estabelecer ligação a uma conta existente
|
||||||
openid_connect_desc=O URI do OpenID escolhido Ă© desconhecido. Associe-o a uma nova conta aqui.
|
openid_connect_desc=O URI do OpenID escolhido Ă© desconhecido. Associe-o a uma nova conta aqui.
|
||||||
|
|
|
@ -630,48 +630,14 @@ func MoveIssues(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type movedIssuesForm struct {
|
form := &issues_model.ProjectMovedIssuesForm{}
|
||||||
Issues []struct {
|
|
||||||
IssueID int64 `json:"issueID"`
|
|
||||||
Sorting int64 `json:"sorting"`
|
|
||||||
} `json:"issues"`
|
|
||||||
}
|
|
||||||
|
|
||||||
form := &movedIssuesForm{}
|
|
||||||
if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
|
if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
|
||||||
ctx.ServerError("DecodeMovedIssuesForm", err)
|
ctx.ServerError("DecodeMovedIssuesForm", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
issueIDs := make([]int64, 0, len(form.Issues))
|
err = issues_model.MoveIssuesOnProjectBoard(ctx, ctx.Doer, form, project, board)
|
||||||
sortedIssueIDs := make(map[int64]int64)
|
|
||||||
for _, issue := range form.Issues {
|
|
||||||
issueIDs = append(issueIDs, issue.IssueID)
|
|
||||||
sortedIssueIDs[issue.Sorting] = issue.IssueID
|
|
||||||
}
|
|
||||||
movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.NotFoundOrServerError("GetIssueByID", issues_model.IsErrIssueNotExist, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(movedIssues) != len(form.Issues) {
|
|
||||||
ctx.ServerError("some issues do not exist", errors.New("some issues do not exist"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = movedIssues.LoadRepositories(ctx); err != nil {
|
|
||||||
ctx.ServerError("LoadRepositories", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, issue := range movedIssues {
|
|
||||||
if issue.RepoID != project.RepoID && issue.Repo.OwnerID != project.OwnerID {
|
|
||||||
ctx.ServerError("Some issue's repoID is not equal to project's repoID", errors.New("Some issue's repoID is not equal to project's repoID"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil {
|
|
||||||
ctx.ServerError("MoveIssuesOnProjectBoard", err)
|
ctx.ServerError("MoveIssuesOnProjectBoard", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1662,11 +1662,15 @@ func ViewIssue(ctx *context.Context) {
|
||||||
if comment.MilestoneID > 0 && comment.Milestone == nil {
|
if comment.MilestoneID > 0 && comment.Milestone == nil {
|
||||||
comment.Milestone = ghostMilestone
|
comment.Milestone = ghostMilestone
|
||||||
}
|
}
|
||||||
} else if comment.Type == issues_model.CommentTypeProject {
|
} else if comment.Type == issues_model.CommentTypeProject || comment.Type == issues_model.CommentTypeProjectBoard {
|
||||||
if err = comment.LoadProject(ctx); err != nil {
|
if err = comment.LoadProject(ctx); err != nil {
|
||||||
ctx.ServerError("LoadProject", err)
|
ctx.ServerError("LoadProject", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err = comment.LoadProjectBoard(); err != nil {
|
||||||
|
ctx.ServerError("LoadProjectBoard", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ghostProject := &project_model.Project{
|
ghostProject := &project_model.Project{
|
||||||
ID: -1,
|
ID: -1,
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -619,47 +618,14 @@ func MoveIssues(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type movedIssuesForm struct {
|
form := &issues_model.ProjectMovedIssuesForm{}
|
||||||
Issues []struct {
|
|
||||||
IssueID int64 `json:"issueID"`
|
|
||||||
Sorting int64 `json:"sorting"`
|
|
||||||
} `json:"issues"`
|
|
||||||
}
|
|
||||||
|
|
||||||
form := &movedIssuesForm{}
|
|
||||||
if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
|
if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
|
||||||
ctx.ServerError("DecodeMovedIssuesForm", err)
|
ctx.ServerError("DecodeMovedIssuesForm", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
issueIDs := make([]int64, 0, len(form.Issues))
|
err = issues_model.MoveIssuesOnProjectBoard(ctx, ctx.Doer, form, project, board)
|
||||||
sortedIssueIDs := make(map[int64]int64)
|
|
||||||
for _, issue := range form.Issues {
|
|
||||||
issueIDs = append(issueIDs, issue.IssueID)
|
|
||||||
sortedIssueIDs[issue.Sorting] = issue.IssueID
|
|
||||||
}
|
|
||||||
movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
|
|
||||||
if err != nil {
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, issue := range movedIssues {
|
|
||||||
if issue.RepoID != project.RepoID {
|
|
||||||
ctx.ServerError("Some issue's repoID is not equal to project's repoID", errors.New("Some issue's repoID is not equal to project's repoID"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil {
|
|
||||||
ctx.ServerError("MoveIssuesOnProjectBoard", err)
|
ctx.ServerError("MoveIssuesOnProjectBoard", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{if $.IsSplitStyle}}
|
{{if $.IsSplitStyle}}
|
||||||
{{range $k, $line := $.section.Lines}}
|
{{range $k, $line := $.section.Lines}}
|
||||||
<tr class="{{.GetHTMLDiffLineType}}-code nl-{{$k}} ol-{{$k}}">
|
<tr class="{{.GetHTMLDiffLineType}}-code nl-{{$k}} ol-{{$k}} line-expanded">
|
||||||
{{if eq .GetType 4}}
|
{{if eq .GetType 4}}
|
||||||
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}">
|
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}">
|
||||||
<div class="tw-flex">
|
<div class="tw-flex">
|
||||||
|
@ -26,17 +26,17 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
|
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
|
||||||
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$.FileNameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
|
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{$.FileNameHash}}L{{$line.LeftIdx}}{{end}}"></span></td>
|
||||||
<td class="blob-excerpt lines-escape lines-escape-old">{{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
<td class="lines-escape lines-escape-old">{{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
||||||
<td class="blob-excerpt lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
|
<td class="lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
|
||||||
<td class="blob-excerpt lines-code lines-code-old">{{/*
|
<td class="lines-code lines-code-old">{{/*
|
||||||
*/}}{{if $line.LeftIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/*
|
*/}}{{if $line.LeftIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/*
|
||||||
*/}}<code class="code-inner"></code>{{/*
|
*/}}<code class="code-inner"></code>{{/*
|
||||||
*/}}{{end}}{{/*
|
*/}}{{end}}{{/*
|
||||||
*/}}</td>
|
*/}}</td>
|
||||||
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
|
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
|
||||||
<td class="blob-excerpt lines-escape lines-escape-new">{{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
<td class="lines-escape lines-escape-new">{{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
||||||
<td class="blob-excerpt lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
|
<td class="lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="tw-font-mono" data-type-marker=""></span>{{end}}</td>
|
||||||
<td class="blob-excerpt lines-code lines-code-new">{{/*
|
<td class="lines-code lines-code-new">{{/*
|
||||||
*/}}{{if $line.RightIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/*
|
*/}}{{if $line.RightIdx}}{{template "repo/diff/section_code" dict "diff" $inlineDiff}}{{else}}{{/*
|
||||||
*/}}<code class="code-inner"></code>{{/*
|
*/}}<code class="code-inner"></code>{{/*
|
||||||
*/}}{{end}}{{/*
|
*/}}{{end}}{{/*
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{range $k, $line := $.section.Lines}}
|
{{range $k, $line := $.section.Lines}}
|
||||||
<tr class="{{.GetHTMLDiffLineType}}-code nl-{{$k}} ol-{{$k}}">
|
<tr class="{{.GetHTMLDiffLineType}}-code nl-{{$k}} ol-{{$k}} line-expanded">
|
||||||
{{if eq .GetType 4}}
|
{{if eq .GetType 4}}
|
||||||
<td colspan="2" class="lines-num">
|
<td colspan="2" class="lines-num">
|
||||||
<div class="tw-flex">
|
<div class="tw-flex">
|
||||||
|
@ -72,9 +72,9 @@
|
||||||
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
|
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{$.FileNameHash}}R{{$line.RightIdx}}{{end}}"></span></td>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
|
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line ctx.Locale}}
|
||||||
<td class="blob-excerpt lines-escape">{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
<td class="lines-escape">{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}</td>
|
||||||
<td class="blob-excerpt lines-type-marker"><span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
|
<td class="lines-type-marker"><span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
|
||||||
<td class="blob-excerpt lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}"><code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td>
|
<td class="lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}"><code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -600,6 +600,26 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{else if eq .Type 31}}
|
||||||
|
{{if not $.UnitProjectsGlobalDisabled}}
|
||||||
|
<div class="timeline-item event" id="{{.HashTag}}">
|
||||||
|
<span class="badge">{{svg "octicon-project"}}</span>
|
||||||
|
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
||||||
|
<span class="text grey muted-links">
|
||||||
|
{{template "shared/user/authorlink" .Poster}}
|
||||||
|
|
||||||
|
{{$projectDisplayHtml := "Unknown Project"}}
|
||||||
|
{{if .Project}}
|
||||||
|
{{$trKey := printf "projects.type-%d.display_name" .Project.Type}}
|
||||||
|
{{$projectDisplayHtml = HTMLFormat `<span data-tooltip-content="%s">%s</span>` (ctx.Locale.Tr $trKey) .Project.Title}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if gt .ProjectID 0}}
|
||||||
|
{{ctx.Locale.Tr "repo.issues.change_project_board_at" .ProjectBoard.FromBoardTitle .ProjectBoard.ToBoardTitle $projectDisplayHtml $createdStr}}
|
||||||
|
{{end}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
{{else if eq .Type 32}}
|
{{else if eq .Type 32}}
|
||||||
<div class="timeline-item-group">
|
<div class="timeline-item-group">
|
||||||
<div class="timeline-item event" id="{{.HashTag}}">
|
<div class="timeline-item event" id="{{.HashTag}}">
|
||||||
|
|
|
@ -2377,7 +2377,7 @@ tbody.commit-list {
|
||||||
|
|
||||||
.tag-code,
|
.tag-code,
|
||||||
.tag-code td,
|
.tag-code td,
|
||||||
.tag-code .blob-excerpt {
|
.tag-code.line-expanded {
|
||||||
background-color: var(--color-box-body-highlight);
|
background-color: var(--color-box-body-highlight);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
@ -2393,8 +2393,8 @@ tbody.commit-list {
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blob-excerpt {
|
.line-expanded {
|
||||||
background-color: var(--color-secondary-alpha-30);
|
background-color: var(--color-secondary-alpha-20);
|
||||||
}
|
}
|
||||||
|
|
||||||
.issue-keyword {
|
.issue-keyword {
|
||||||
|
@ -2553,11 +2553,9 @@ tbody.commit-list {
|
||||||
|
|
||||||
.code-diff-unified .add-code,
|
.code-diff-unified .add-code,
|
||||||
.code-diff-unified .add-code td,
|
.code-diff-unified .add-code td,
|
||||||
.code-diff-split .add-code .lines-num-new,
|
|
||||||
.code-diff-split .add-code .lines-type-marker-new,
|
.code-diff-split .add-code .lines-type-marker-new,
|
||||||
.code-diff-split .add-code .lines-escape-new,
|
.code-diff-split .add-code .lines-escape-new,
|
||||||
.code-diff-split .add-code .lines-code-new,
|
.code-diff-split .add-code .lines-code-new,
|
||||||
.code-diff-split .del-code .add-code.lines-num-new,
|
|
||||||
.code-diff-split .del-code .add-code.lines-type-marker-new,
|
.code-diff-split .del-code .add-code.lines-type-marker-new,
|
||||||
.code-diff-split .del-code .add-code.lines-escape-new,
|
.code-diff-split .del-code .add-code.lines-escape-new,
|
||||||
.code-diff-split .del-code .add-code.lines-code-new {
|
.code-diff-split .del-code .add-code.lines-code-new {
|
||||||
|
@ -2565,17 +2563,33 @@ tbody.commit-list {
|
||||||
border-color: var(--color-diff-added-row-border);
|
border-color: var(--color-diff-added-row-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-diff-split .del-code .lines-num-new,
|
|
||||||
.code-diff-split .del-code .lines-type-marker-new,
|
.code-diff-split .del-code .lines-type-marker-new,
|
||||||
.code-diff-split .del-code .lines-code-new,
|
.code-diff-split .del-code .lines-code-new,
|
||||||
.code-diff-split .del-code .lines-escape-new,
|
.code-diff-split .del-code .lines-escape-new,
|
||||||
.code-diff-split .add-code .lines-num-old,
|
|
||||||
.code-diff-split .add-code .lines-escape-old,
|
.code-diff-split .add-code .lines-escape-old,
|
||||||
.code-diff-split .add-code .lines-type-marker-old,
|
.code-diff-split .add-code .lines-type-marker-old,
|
||||||
.code-diff-split .add-code .lines-code-old {
|
.code-diff-split .add-code .lines-code-old {
|
||||||
background: var(--color-diff-inactive);
|
background: var(--color-diff-inactive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code-diff-split .add-code .lines-num.lines-num-old,
|
||||||
|
.code-diff-split .del-code .lines-num.lines-num-new {
|
||||||
|
background: var(--color-diff-inactive);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-diff-unified .del-code .lines-num,
|
||||||
|
.code-diff-split .del-code .lines-num {
|
||||||
|
background: var(--color-diff-removed-linenum-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-diff-unified .add-code .lines-num,
|
||||||
|
.code-diff-split .add-code .lines-num,
|
||||||
|
.code-diff-split .del-code .add-code.lines-num {
|
||||||
|
background: var(--color-diff-added-linenum-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
.code-diff-split tbody tr td:nth-child(5),
|
.code-diff-split tbody tr td:nth-child(5),
|
||||||
.code-diff-split tbody tr td.add-comment-right {
|
.code-diff-split tbody tr td.add-comment-right {
|
||||||
border-left: 1px solid var(--color-secondary);
|
border-left: 1px solid var(--color-secondary);
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
/* red/green colorblind-friendly colors */
|
/* red/green colorblind-friendly colors */
|
||||||
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
|
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
|
||||||
:root {
|
:root {
|
||||||
--color-diff-added-word-bg: #388bfd66;
|
--color-diff-added-linenum-bg: #1979fd46;
|
||||||
--color-diff-added-row-bg: #388bfd26;
|
--color-diff-added-row-bg: #1979fd20;
|
||||||
|
--color-diff-added-word-bg: #1979fd66;
|
||||||
--color-diff-removed-word-bg: #db6d2866;
|
--color-diff-removed-linenum-bg: #c8622146;
|
||||||
--color-diff-removed-row-bg: #db6d2826;
|
--color-diff-removed-row-bg: #c8622120;
|
||||||
|
--color-diff-removed-word-bg: #c8622166;
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,14 +143,16 @@
|
||||||
--color-grey-light: #818f9e;
|
--color-grey-light: #818f9e;
|
||||||
--color-gold: #b1983b;
|
--color-gold: #b1983b;
|
||||||
--color-white: #ffffff;
|
--color-white: #ffffff;
|
||||||
--color-diff-removed-word-bg: #6f3333;
|
--color-diff-added-linenum-bg: #274227;
|
||||||
--color-diff-added-word-bg: #3c653c;
|
--color-diff-added-row-bg: #203224;
|
||||||
--color-diff-removed-row-bg: #3c2626;
|
|
||||||
--color-diff-moved-row-bg: #818044;
|
|
||||||
--color-diff-added-row-bg: #283e2d;
|
|
||||||
--color-diff-removed-row-border: #634343;
|
|
||||||
--color-diff-moved-row-border: #bcca6f;
|
|
||||||
--color-diff-added-row-border: #314a37;
|
--color-diff-added-row-border: #314a37;
|
||||||
|
--color-diff-added-word-bg: #3c653c;
|
||||||
|
--color-diff-moved-row-bg: #818044;
|
||||||
|
--color-diff-moved-row-border: #bcca6f;
|
||||||
|
--color-diff-removed-linenum-bg: #482121;
|
||||||
|
--color-diff-removed-row-bg: #301e1e;
|
||||||
|
--color-diff-removed-row-border: #634343;
|
||||||
|
--color-diff-removed-word-bg: #6f3333;
|
||||||
--color-diff-inactive: #22282d;
|
--color-diff-inactive: #22282d;
|
||||||
--color-error-border: #a04141;
|
--color-error-border: #a04141;
|
||||||
--color-error-bg: #522;
|
--color-error-bg: #522;
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
/* red/green colorblind-friendly colors */
|
/* red/green colorblind-friendly colors */
|
||||||
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
|
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
|
||||||
:root {
|
:root {
|
||||||
--color-diff-added-word-bg: #54aeff66;
|
--color-diff-added-linenum-bg: #54aeff4d;
|
||||||
--color-diff-added-row-bg: #ddf4ff80;
|
--color-diff-added-row-bg: #ddf4ff80;
|
||||||
|
--color-diff-added-word-bg: #54aeff66;
|
||||||
--color-diff-removed-word-bg: #ffb77c80;
|
--color-diff-removed-linenum-bg: #ffb77c4d;
|
||||||
--color-diff-removed-row-bg: #fff1e580;
|
--color-diff-removed-row-bg: #fff1e580;
|
||||||
|
--color-diff-removed-word-bg: #ffb77c80;
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,14 +143,16 @@
|
||||||
--color-grey-light: #7c838a;
|
--color-grey-light: #7c838a;
|
||||||
--color-gold: #a1882b;
|
--color-gold: #a1882b;
|
||||||
--color-white: #ffffff;
|
--color-white: #ffffff;
|
||||||
--color-diff-removed-word-bg: #fdb8c0;
|
--color-diff-added-linenum-bg: #d1f8d9;
|
||||||
--color-diff-added-word-bg: #acf2bd;
|
|
||||||
--color-diff-removed-row-bg: #ffeef0;
|
|
||||||
--color-diff-moved-row-bg: #f1f8d1;
|
|
||||||
--color-diff-added-row-bg: #e6ffed;
|
--color-diff-added-row-bg: #e6ffed;
|
||||||
--color-diff-removed-row-border: #f1c0c0;
|
|
||||||
--color-diff-moved-row-border: #d0e27f;
|
|
||||||
--color-diff-added-row-border: #e6ffed;
|
--color-diff-added-row-border: #e6ffed;
|
||||||
|
--color-diff-added-word-bg: #acf2bd;
|
||||||
|
--color-diff-moved-row-bg: #f1f8d1;
|
||||||
|
--color-diff-moved-row-border: #d0e27f;
|
||||||
|
--color-diff-removed-linenum-bg: #ffcecb;
|
||||||
|
--color-diff-removed-row-bg: #ffeef0;
|
||||||
|
--color-diff-removed-row-border: #f1c0c0;
|
||||||
|
--color-diff-removed-word-bg: #fdb8c0;
|
||||||
--color-diff-inactive: #f0f2f4;
|
--color-diff-inactive: #f0f2f4;
|
||||||
--color-error-border: #e0b4b4;
|
--color-error-border: #e0b4b4;
|
||||||
--color-error-bg: #fff6f6;
|
--color-error-bg: #fff6f6;
|
||||||
|
|
Loading…
Reference in New Issue