mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-10-31 22:58:59 +01:00
Add default board to new projects, remove uncategorized pseudo-board (#29874)
On creation of an empty project (no template) a default board will be created instead of falling back to the uneditable pseudo-board. Every project now has to have exactly one default boards. As a consequence, you cannot unset a board as default, instead you have to set another board as default. Existing projects will be modified using a cron job, additionally this check will run every midnight by default. Deleting the default board is not allowed, you have to set another board as default to do it. Fixes #29873 Fixes #14679 along the way Fixes #29853 Co-authored-by: delvh <dev.lh@web.de> (cherry picked from commit e5160185ed65fd1c2bcb2fc7dc7e0b5514ddb299) Conflicts: options/locale/locale_en-US.ini trivial conflict because Forgejo strings do not have surrounding double quotes
This commit is contained in:
parent
b019ecce89
commit
8ffb9c6fb1
17 changed files with 400 additions and 196 deletions
|
@ -45,3 +45,27 @@
|
||||||
type: 2
|
type: 2
|
||||||
created_unix: 1688973000
|
created_unix: 1688973000
|
||||||
updated_unix: 1688973000
|
updated_unix: 1688973000
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 5
|
||||||
|
title: project without default column
|
||||||
|
owner_id: 2
|
||||||
|
repo_id: 0
|
||||||
|
is_closed: false
|
||||||
|
creator_id: 2
|
||||||
|
board_type: 1
|
||||||
|
type: 2
|
||||||
|
created_unix: 1688973000
|
||||||
|
updated_unix: 1688973000
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 6
|
||||||
|
title: project with multiple default columns
|
||||||
|
owner_id: 2
|
||||||
|
repo_id: 0
|
||||||
|
is_closed: false
|
||||||
|
creator_id: 2
|
||||||
|
board_type: 1
|
||||||
|
type: 2
|
||||||
|
created_unix: 1688973000
|
||||||
|
updated_unix: 1688973000
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
project_id: 1
|
project_id: 1
|
||||||
title: To Do
|
title: To Do
|
||||||
creator_id: 2
|
creator_id: 2
|
||||||
|
default: true
|
||||||
created_unix: 1588117528
|
created_unix: 1588117528
|
||||||
updated_unix: 1588117528
|
updated_unix: 1588117528
|
||||||
|
|
||||||
|
@ -29,3 +30,48 @@
|
||||||
creator_id: 2
|
creator_id: 2
|
||||||
created_unix: 1588117528
|
created_unix: 1588117528
|
||||||
updated_unix: 1588117528
|
updated_unix: 1588117528
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 5
|
||||||
|
project_id: 2
|
||||||
|
title: Backlog
|
||||||
|
creator_id: 2
|
||||||
|
default: true
|
||||||
|
created_unix: 1588117528
|
||||||
|
updated_unix: 1588117528
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 6
|
||||||
|
project_id: 4
|
||||||
|
title: Backlog
|
||||||
|
creator_id: 2
|
||||||
|
default: true
|
||||||
|
created_unix: 1588117528
|
||||||
|
updated_unix: 1588117528
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 7
|
||||||
|
project_id: 5
|
||||||
|
title: Done
|
||||||
|
creator_id: 2
|
||||||
|
default: false
|
||||||
|
created_unix: 1588117528
|
||||||
|
updated_unix: 1588117528
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 8
|
||||||
|
project_id: 6
|
||||||
|
title: Backlog
|
||||||
|
creator_id: 2
|
||||||
|
default: true
|
||||||
|
created_unix: 1588117528
|
||||||
|
updated_unix: 1588117528
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 9
|
||||||
|
project_id: 6
|
||||||
|
title: Uncategorized
|
||||||
|
creator_id: 2
|
||||||
|
default: true
|
||||||
|
created_unix: 1588117528
|
||||||
|
updated_unix: 1588117528
|
||||||
|
|
|
@ -49,18 +49,13 @@ func (issue *Issue) ProjectBoardID(ctx context.Context) int64 {
|
||||||
|
|
||||||
// LoadIssuesFromBoard load issues assigned to this board
|
// LoadIssuesFromBoard load issues assigned to this board
|
||||||
func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) {
|
func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) {
|
||||||
issueList := make(IssueList, 0, 10)
|
issueList, err := Issues(ctx, &IssuesOptions{
|
||||||
|
ProjectBoardID: b.ID,
|
||||||
if b.ID > 0 {
|
ProjectID: b.ProjectID,
|
||||||
issues, err := Issues(ctx, &IssuesOptions{
|
SortType: "project-column-sorting",
|
||||||
ProjectBoardID: b.ID,
|
})
|
||||||
ProjectID: b.ProjectID,
|
if err != nil {
|
||||||
SortType: "project-column-sorting",
|
return nil, err
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
issueList = issues
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.Default {
|
if b.Default {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
-
|
||||||
|
id: 1
|
||||||
|
title: project without default column
|
||||||
|
owner_id: 2
|
||||||
|
repo_id: 0
|
||||||
|
is_closed: false
|
||||||
|
creator_id: 2
|
||||||
|
board_type: 1
|
||||||
|
type: 2
|
||||||
|
created_unix: 1688973000
|
||||||
|
updated_unix: 1688973000
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 2
|
||||||
|
title: project with multiple default columns
|
||||||
|
owner_id: 2
|
||||||
|
repo_id: 0
|
||||||
|
is_closed: false
|
||||||
|
creator_id: 2
|
||||||
|
board_type: 1
|
||||||
|
type: 2
|
||||||
|
created_unix: 1688973000
|
||||||
|
updated_unix: 1688973000
|
|
@ -0,0 +1,26 @@
|
||||||
|
-
|
||||||
|
id: 1
|
||||||
|
project_id: 1
|
||||||
|
title: Done
|
||||||
|
creator_id: 2
|
||||||
|
default: false
|
||||||
|
created_unix: 1588117528
|
||||||
|
updated_unix: 1588117528
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 2
|
||||||
|
project_id: 2
|
||||||
|
title: Backlog
|
||||||
|
creator_id: 2
|
||||||
|
default: true
|
||||||
|
created_unix: 1588117528
|
||||||
|
updated_unix: 1588117528
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 3
|
||||||
|
project_id: 2
|
||||||
|
title: Uncategorized
|
||||||
|
creator_id: 2
|
||||||
|
default: true
|
||||||
|
created_unix: 1588117528
|
||||||
|
updated_unix: 1588117528
|
|
@ -570,6 +570,8 @@ var migrations = []Migration{
|
||||||
NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
|
NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
|
||||||
// v291 -> v292
|
// v291 -> v292
|
||||||
NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment),
|
NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment),
|
||||||
|
// v292 -> v293
|
||||||
|
NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency),
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current db version
|
// GetCurrentDBVersion returns the current db version
|
||||||
|
|
85
models/migrations/v1_22/v292.go
Normal file
85
models/migrations/v1_22/v292.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_22 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/project"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckProjectColumnsConsistency ensures there is exactly one default board per project present
|
||||||
|
func CheckProjectColumnsConsistency(x *xorm.Engine) error {
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := setting.Database.IterateBufferSize
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 50
|
||||||
|
}
|
||||||
|
|
||||||
|
start := 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
var projects []project.Project
|
||||||
|
if err := sess.SQL("SELECT DISTINCT `p`.`id`, `p`.`creator_id` FROM `project` `p` WHERE (SELECT COUNT(*) FROM `project_board` `pb` WHERE `pb`.`project_id` = `p`.`id` AND `pb`.`default` = ?) != 1", true).
|
||||||
|
Limit(limit, start).
|
||||||
|
Find(&projects); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(projects) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
start += len(projects)
|
||||||
|
|
||||||
|
for _, p := range projects {
|
||||||
|
var boards []project.Board
|
||||||
|
if err := sess.Where("project_id=? AND `default` = ?", p.ID, true).OrderBy("sorting").Find(&boards); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(boards) == 0 {
|
||||||
|
if _, err := sess.Insert(project.Board{
|
||||||
|
ProjectID: p.ID,
|
||||||
|
Default: true,
|
||||||
|
Title: "Uncategorized",
|
||||||
|
CreatorID: p.CreatorID,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var boardsToUpdate []int64
|
||||||
|
for id, b := range boards {
|
||||||
|
if id > 0 {
|
||||||
|
boardsToUpdate = append(boardsToUpdate, b.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := sess.Where(builder.Eq{"project_id": p.ID}.And(builder.In("id", boardsToUpdate))).
|
||||||
|
Cols("`default`").Update(&project.Board{Default: false}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if start%1000 == 0 {
|
||||||
|
if err := sess.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
44
models/migrations/v1_22/v292_test.go
Normal file
44
models/migrations/v1_22/v292_test.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_22 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/migrations/base"
|
||||||
|
"code.gitea.io/gitea/models/project"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_CheckProjectColumnsConsistency(t *testing.T) {
|
||||||
|
// Prepare and load the testing database
|
||||||
|
x, deferable := base.PrepareTestEnv(t, 0, new(project.Project), new(project.Board))
|
||||||
|
defer deferable()
|
||||||
|
if x == nil || t.Failed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, CheckProjectColumnsConsistency(x))
|
||||||
|
|
||||||
|
// check if default board was added
|
||||||
|
var defaultBoard project.Board
|
||||||
|
has, err := x.Where("project_id=? AND `default` = ?", 1, true).Get(&defaultBoard)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, has)
|
||||||
|
assert.Equal(t, int64(1), defaultBoard.ProjectID)
|
||||||
|
assert.True(t, defaultBoard.Default)
|
||||||
|
|
||||||
|
// check if multiple defaults were removed
|
||||||
|
expectDefaultBoard, err := project.GetBoard(db.DefaultContext, 2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(2), expectDefaultBoard.ProjectID)
|
||||||
|
assert.True(t, expectDefaultBoard.Default)
|
||||||
|
|
||||||
|
expectNonDefaultBoard, err := project.GetBoard(db.DefaultContext, 3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(2), expectNonDefaultBoard.ProjectID)
|
||||||
|
assert.False(t, expectNonDefaultBoard.Default)
|
||||||
|
}
|
|
@ -123,6 +123,17 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
board := Board{
|
||||||
|
CreatedUnix: timeutil.TimeStampNow(),
|
||||||
|
CreatorID: project.CreatorID,
|
||||||
|
Title: "Backlog",
|
||||||
|
ProjectID: project.ID,
|
||||||
|
Default: true,
|
||||||
|
}
|
||||||
|
if err := db.Insert(ctx, board); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -176,6 +187,10 @@ func deleteBoardByID(ctx context.Context, boardID int64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if board.Default {
|
||||||
|
return fmt.Errorf("deleteBoardByID: cannot delete default board")
|
||||||
|
}
|
||||||
|
|
||||||
if err = board.removeIssues(ctx); err != nil {
|
if err = board.removeIssues(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -228,7 +243,6 @@ func UpdateBoard(ctx context.Context, board *Board) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBoards fetches all boards related to a project
|
// GetBoards fetches all boards related to a project
|
||||||
// if no default board set, first board is a temporary "Uncategorized" board
|
|
||||||
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
||||||
boards := make([]*Board, 0, 5)
|
boards := make([]*Board, 0, 5)
|
||||||
|
|
||||||
|
@ -244,41 +258,61 @@ 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 create a dummy if none exist
|
// 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 boards []Board
|
||||||
exist, err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, true).Get(&board)
|
if err := db.GetEngine(ctx).Where("project_id=? AND `default` = ?", p.ID, true).OrderBy("sorting").Find(&boards); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if exist {
|
|
||||||
|
// create a default board if none is found
|
||||||
|
if len(boards) == 0 {
|
||||||
|
board := Board{
|
||||||
|
ProjectID: p.ID,
|
||||||
|
Default: true,
|
||||||
|
Title: "Uncategorized",
|
||||||
|
CreatorID: p.CreatorID,
|
||||||
|
}
|
||||||
|
if _, err := db.GetEngine(ctx).Insert(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &board, nil
|
return &board, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// represents a board for issues not assigned to one
|
// unset default boards where too many default boards exist
|
||||||
return &Board{
|
if len(boards) > 1 {
|
||||||
ProjectID: p.ID,
|
var boardsToUpdate []int64
|
||||||
Title: "Uncategorized",
|
for id, b := range boards {
|
||||||
Default: true,
|
if id > 0 {
|
||||||
}, nil
|
boardsToUpdate = append(boardsToUpdate, b.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.GetEngine(ctx).Where(builder.Eq{"project_id": p.ID}.And(builder.In("id", boardsToUpdate))).
|
||||||
|
Cols("`default`").Update(&Board{Default: false}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &boards[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultBoard represents a board for issues not assigned to one
|
// SetDefaultBoard represents a board for issues not assigned to one
|
||||||
// if boardID is 0 unset default
|
|
||||||
func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error {
|
func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error {
|
||||||
_, err := db.GetEngine(ctx).Where(builder.Eq{
|
if _, err := GetBoard(ctx, boardID); err != nil {
|
||||||
"project_id": projectID,
|
|
||||||
"`default`": true,
|
|
||||||
}).Cols("`default`").Update(&Board{Default: false})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if boardID > 0 {
|
if _, err := db.GetEngine(ctx).Where(builder.Eq{
|
||||||
_, err = db.GetEngine(ctx).ID(boardID).Where(builder.Eq{"project_id": projectID}).
|
"project_id": projectID,
|
||||||
Cols("`default`").Update(&Board{Default: true})
|
"`default`": true,
|
||||||
|
}).Cols("`default`").Update(&Board{Default: false}); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err := db.GetEngine(ctx).ID(boardID).Where(builder.Eq{"project_id": projectID}).
|
||||||
|
Cols("`default`").Update(&Board{Default: true})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
40
models/project/board_test.go
Normal file
40
models/project/board_test.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package project
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetDefaultBoard(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
projectWithoutDefault, err := GetProjectByID(db.DefaultContext, 5)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// check if default board was added
|
||||||
|
board, err := projectWithoutDefault.getDefaultBoard(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(5), board.ProjectID)
|
||||||
|
assert.Equal(t, "Uncategorized", board.Title)
|
||||||
|
|
||||||
|
projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// check if multiple defaults were removed
|
||||||
|
board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(6), board.ProjectID)
|
||||||
|
assert.Equal(t, int64(8), board.ID)
|
||||||
|
|
||||||
|
board, err = GetBoard(db.DefaultContext, 9)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(6), board.ProjectID)
|
||||||
|
assert.False(t, board.Default)
|
||||||
|
}
|
|
@ -92,19 +92,19 @@ func TestProjectsSort(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
sortType: "default",
|
sortType: "default",
|
||||||
wants: []int64{1, 3, 2, 4},
|
wants: []int64{1, 3, 2, 6, 5, 4},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sortType: "oldest",
|
sortType: "oldest",
|
||||||
wants: []int64{4, 2, 3, 1},
|
wants: []int64{4, 5, 6, 2, 3, 1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sortType: "recentupdate",
|
sortType: "recentupdate",
|
||||||
wants: []int64{1, 3, 2, 4},
|
wants: []int64{1, 3, 2, 6, 5, 4},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sortType: "leastupdate",
|
sortType: "leastupdate",
|
||||||
wants: []int64{4, 2, 3, 1},
|
wants: []int64{4, 5, 6, 2, 3, 1},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,8 +113,8 @@ func TestProjectsSort(t *testing.T) {
|
||||||
OrderBy: GetSearchOrderByBySortType(tt.sortType),
|
OrderBy: GetSearchOrderByBySortType(tt.sortType),
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, int64(4), count)
|
assert.EqualValues(t, int64(6), count)
|
||||||
if assert.Len(t, projects, 4) {
|
if assert.Len(t, projects, 6) {
|
||||||
for i := range projects {
|
for i := range projects {
|
||||||
assert.EqualValues(t, tt.wants[i], projects[i].ID)
|
assert.EqualValues(t, tt.wants[i], projects[i].ID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1423,7 +1423,6 @@ projects.type.basic_kanban = Basic Kanban
|
||||||
projects.type.bug_triage = Bug Triage
|
projects.type.bug_triage = Bug Triage
|
||||||
projects.template.desc = Template
|
projects.template.desc = Template
|
||||||
projects.template.desc_helper = Select a project template to get started
|
projects.template.desc_helper = Select a project template to get started
|
||||||
projects.type.uncategorized = Uncategorized
|
|
||||||
projects.column.edit = Edit Column
|
projects.column.edit = Edit Column
|
||||||
projects.column.edit_title = Name
|
projects.column.edit_title = Name
|
||||||
projects.column.new_title = Name
|
projects.column.new_title = Name
|
||||||
|
@ -1431,10 +1430,8 @@ projects.column.new_submit = Create Column
|
||||||
projects.column.new = New Column
|
projects.column.new = New Column
|
||||||
projects.column.set_default = Set Default
|
projects.column.set_default = Set Default
|
||||||
projects.column.set_default_desc = Set this column as default for uncategorized issues and pulls
|
projects.column.set_default_desc = Set this column as default for uncategorized issues and pulls
|
||||||
projects.column.unset_default = Unset Default
|
|
||||||
projects.column.unset_default_desc = Unset this column as default
|
|
||||||
projects.column.delete = Delete Column
|
projects.column.delete = Delete Column
|
||||||
projects.column.deletion_desc = Deleting a project column moves all related issues to "Uncategorized". Continue?
|
projects.column.deletion_desc = Deleting a project column moves all related issues to the default column. Continue?
|
||||||
projects.column.color = Color
|
projects.column.color = Color
|
||||||
projects.open = Open
|
projects.open = Open
|
||||||
projects.close = Close
|
projects.close = Close
|
||||||
|
|
|
@ -207,11 +207,7 @@ func ChangeProjectStatus(ctx *context.Context) {
|
||||||
id := ctx.ParamsInt64(":id")
|
id := ctx.ParamsInt64(":id")
|
||||||
|
|
||||||
if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, id, toClose); err != nil {
|
if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, id, toClose); err != nil {
|
||||||
if project_model.IsErrProjectNotExist(err) {
|
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
|
||||||
ctx.NotFound("", err)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("ChangeProjectStatusByRepoIDAndID", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects?state=" + url.QueryEscape(ctx.Params(":action")))
|
ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects?state=" + url.QueryEscape(ctx.Params(":action")))
|
||||||
|
@ -221,11 +217,7 @@ func ChangeProjectStatus(ctx *context.Context) {
|
||||||
func DeleteProject(ctx *context.Context) {
|
func DeleteProject(ctx *context.Context) {
|
||||||
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if project_model.IsErrProjectNotExist(err) {
|
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
|
||||||
ctx.NotFound("", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetProjectByID", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if p.OwnerID != ctx.ContextUser.ID {
|
if p.OwnerID != ctx.ContextUser.ID {
|
||||||
|
@ -254,11 +246,7 @@ func RenderEditProject(ctx *context.Context) {
|
||||||
|
|
||||||
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if project_model.IsErrProjectNotExist(err) {
|
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
|
||||||
ctx.NotFound("", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetProjectByID", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if p.OwnerID != ctx.ContextUser.ID {
|
if p.OwnerID != ctx.ContextUser.ID {
|
||||||
|
@ -303,11 +291,7 @@ func EditProjectPost(ctx *context.Context) {
|
||||||
|
|
||||||
p, err := project_model.GetProjectByID(ctx, projectID)
|
p, err := project_model.GetProjectByID(ctx, projectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if project_model.IsErrProjectNotExist(err) {
|
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
|
||||||
ctx.NotFound("", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetProjectByID", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if p.OwnerID != ctx.ContextUser.ID {
|
if p.OwnerID != ctx.ContextUser.ID {
|
||||||
|
@ -335,11 +319,7 @@ func EditProjectPost(ctx *context.Context) {
|
||||||
func ViewProject(ctx *context.Context) {
|
func ViewProject(ctx *context.Context) {
|
||||||
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if project_model.IsErrProjectNotExist(err) {
|
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
|
||||||
ctx.NotFound("", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetProjectByID", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if project.OwnerID != ctx.ContextUser.ID {
|
if project.OwnerID != ctx.ContextUser.ID {
|
||||||
|
@ -353,10 +333,6 @@ func ViewProject(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if boards[0].ID == 0 {
|
|
||||||
boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
|
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("LoadIssuesOfBoards", err)
|
ctx.ServerError("LoadIssuesOfBoards", err)
|
||||||
|
@ -493,11 +469,7 @@ func DeleteProjectBoard(ctx *context.Context) {
|
||||||
|
|
||||||
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if project_model.IsErrProjectNotExist(err) {
|
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
|
||||||
ctx.NotFound("", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetProjectByID", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,11 +506,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
|
||||||
|
|
||||||
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if project_model.IsErrProjectNotExist(err) {
|
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
|
||||||
ctx.NotFound("", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetProjectByID", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,11 +534,7 @@ func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr
|
||||||
|
|
||||||
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if project_model.IsErrProjectNotExist(err) {
|
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
|
||||||
ctx.NotFound("", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetProjectByID", err)
|
|
||||||
}
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,21 +600,6 @@ func SetDefaultProjectBoard(ctx *context.Context) {
|
||||||
ctx.JSONOK()
|
ctx.JSONOK()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsetDefaultProjectBoard unset default board for uncategorized issues/pulls
|
|
||||||
func UnsetDefaultProjectBoard(ctx *context.Context) {
|
|
||||||
project, _ := CheckProjectBoardChangePermissions(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := project_model.SetDefaultBoard(ctx, project.ID, 0); err != nil {
|
|
||||||
ctx.ServerError("SetDefaultBoard", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSONOK()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveIssues moves or keeps issues in a column and sorts them inside that column
|
// MoveIssues moves or keeps issues in a column and sorts them inside that column
|
||||||
func MoveIssues(ctx *context.Context) {
|
func MoveIssues(ctx *context.Context) {
|
||||||
if ctx.Doer == nil {
|
if ctx.Doer == nil {
|
||||||
|
@ -662,11 +611,7 @@ func MoveIssues(ctx *context.Context) {
|
||||||
|
|
||||||
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if project_model.IsErrProjectNotExist(err) {
|
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
|
||||||
ctx.NotFound("ProjectNotExist", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetProjectByID", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if project.OwnerID != ctx.ContextUser.ID {
|
if project.OwnerID != ctx.ContextUser.ID {
|
||||||
|
@ -674,28 +619,15 @@ func MoveIssues(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var board *project_model.Board
|
board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFoundOrServerError("GetProjectBoard", project_model.IsErrProjectBoardNotExist, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.ParamsInt64(":boardID") == 0 {
|
if board.ProjectID != project.ID {
|
||||||
board = &project_model.Board{
|
ctx.NotFound("BoardNotInProject", nil)
|
||||||
ID: 0,
|
return
|
||||||
ProjectID: project.ID,
|
|
||||||
Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
|
|
||||||
if err != nil {
|
|
||||||
if project_model.IsErrProjectBoardNotExist(err) {
|
|
||||||
ctx.NotFound("ProjectBoardNotExist", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetProjectBoard", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if board.ProjectID != project.ID {
|
|
||||||
ctx.NotFound("BoardNotInProject", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type movedIssuesForm struct {
|
type movedIssuesForm struct {
|
||||||
|
@ -718,11 +650,7 @@ func MoveIssues(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
|
movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if issues_model.IsErrIssueNotExist(err) {
|
ctx.NotFoundOrServerError("GetIssueByID", issues_model.IsErrIssueNotExist, err)
|
||||||
ctx.NotFound("IssueNotExisting", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetIssueByID", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -314,10 +314,6 @@ func ViewProject(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if boards[0].ID == 0 {
|
|
||||||
boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
|
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("LoadIssuesOfBoards", err)
|
ctx.ServerError("LoadIssuesOfBoards", err)
|
||||||
|
@ -582,21 +578,6 @@ func SetDefaultProjectBoard(ctx *context.Context) {
|
||||||
ctx.JSONOK()
|
ctx.JSONOK()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnSetDefaultProjectBoard unset default board for uncategorized issues/pulls
|
|
||||||
func UnSetDefaultProjectBoard(ctx *context.Context) {
|
|
||||||
project, _ := checkProjectBoardChangePermissions(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := project_model.SetDefaultBoard(ctx, project.ID, 0); err != nil {
|
|
||||||
ctx.ServerError("SetDefaultBoard", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSONOK()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveIssues moves or keeps issues in a column and sorts them inside that column
|
// MoveIssues moves or keeps issues in a column and sorts them inside that column
|
||||||
func MoveIssues(ctx *context.Context) {
|
func MoveIssues(ctx *context.Context) {
|
||||||
if ctx.Doer == nil {
|
if ctx.Doer == nil {
|
||||||
|
@ -627,28 +608,19 @@ func MoveIssues(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var board *project_model.Board
|
board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
|
||||||
|
if err != nil {
|
||||||
|
if project_model.IsErrProjectBoardNotExist(err) {
|
||||||
|
ctx.NotFound("ProjectBoardNotExist", nil)
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("GetProjectBoard", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.ParamsInt64(":boardID") == 0 {
|
if board.ProjectID != project.ID {
|
||||||
board = &project_model.Board{
|
ctx.NotFound("BoardNotInProject", nil)
|
||||||
ID: 0,
|
return
|
||||||
ProjectID: project.ID,
|
|
||||||
Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
|
|
||||||
if err != nil {
|
|
||||||
if project_model.IsErrProjectBoardNotExist(err) {
|
|
||||||
ctx.NotFound("ProjectBoardNotExist", nil)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetProjectBoard", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if board.ProjectID != project.ID {
|
|
||||||
ctx.NotFound("BoardNotInProject", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type movedIssuesForm struct {
|
type movedIssuesForm struct {
|
||||||
|
|
|
@ -986,7 +986,6 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Put("", web.Bind(forms.EditProjectBoardForm{}), org.EditProjectBoard)
|
m.Put("", web.Bind(forms.EditProjectBoardForm{}), org.EditProjectBoard)
|
||||||
m.Delete("", org.DeleteProjectBoard)
|
m.Delete("", org.DeleteProjectBoard)
|
||||||
m.Post("/default", org.SetDefaultProjectBoard)
|
m.Post("/default", org.SetDefaultProjectBoard)
|
||||||
m.Post("/unsetdefault", org.UnsetDefaultProjectBoard)
|
|
||||||
|
|
||||||
m.Post("/move", org.MoveIssues)
|
m.Post("/move", org.MoveIssues)
|
||||||
})
|
})
|
||||||
|
@ -1360,7 +1359,6 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Put("", web.Bind(forms.EditProjectBoardForm{}), repo.EditProjectBoard)
|
m.Put("", web.Bind(forms.EditProjectBoardForm{}), repo.EditProjectBoard)
|
||||||
m.Delete("", repo.DeleteProjectBoard)
|
m.Delete("", repo.DeleteProjectBoard)
|
||||||
m.Post("/default", repo.SetDefaultProjectBoard)
|
m.Post("/default", repo.SetDefaultProjectBoard)
|
||||||
m.Post("/unsetdefault", repo.UnSetDefaultProjectBoard)
|
|
||||||
|
|
||||||
m.Post("/move", repo.MoveIssues)
|
m.Post("/move", repo.MoveIssues)
|
||||||
})
|
})
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{.Title}}
|
{{.Title}}
|
||||||
</div>
|
</div>
|
||||||
{{if and $canWriteProject (ne .ID 0)}}
|
{{if $canWriteProject}}
|
||||||
<div class="ui dropdown jump item">
|
<div class="ui dropdown jump item">
|
||||||
<div class="tw-px-2">
|
<div class="tw-px-2">
|
||||||
{{svg "octicon-kebab-horizontal"}}
|
{{svg "octicon-kebab-horizontal"}}
|
||||||
|
@ -86,29 +86,20 @@
|
||||||
</a>
|
</a>
|
||||||
{{if not .Default}}
|
{{if not .Default}}
|
||||||
<a class="item show-modal button default-project-column-show"
|
<a class="item show-modal button default-project-column-show"
|
||||||
data-modal="#default-project-column-modal-{{.ID}}"
|
data-modal="#default-project-column-modal-{{.ID}}"
|
||||||
data-modal-default-project-column-header="{{ctx.Locale.Tr "repo.projects.column.set_default"}}"
|
data-modal-default-project-column-header="{{ctx.Locale.Tr "repo.projects.column.set_default"}}"
|
||||||
data-modal-default-project-column-content="{{ctx.Locale.Tr "repo.projects.column.set_default_desc"}}"
|
data-modal-default-project-column-content="{{ctx.Locale.Tr "repo.projects.column.set_default_desc"}}"
|
||||||
data-url="{{$.Link}}/{{.ID}}/default">
|
data-url="{{$.Link}}/{{.ID}}/default">
|
||||||
{{svg "octicon-pin"}}
|
{{svg "octicon-pin"}}
|
||||||
{{ctx.Locale.Tr "repo.projects.column.set_default"}}
|
{{ctx.Locale.Tr "repo.projects.column.set_default"}}
|
||||||
</a>
|
</a>
|
||||||
{{else}}
|
<a class="item show-modal button show-delete-project-column-modal"
|
||||||
<a class="item show-modal button default-project-column-show"
|
data-modal="#delete-project-column-modal-{{.ID}}"
|
||||||
data-modal="#default-project-column-modal-{{.ID}}"
|
data-url="{{$.Link}}/{{.ID}}">
|
||||||
data-modal-default-project-column-header="{{ctx.Locale.Tr "repo.projects.column.unset_default"}}"
|
{{svg "octicon-trash"}}
|
||||||
data-modal-default-project-column-content="{{ctx.Locale.Tr "repo.projects.column.unset_default_desc"}}"
|
{{ctx.Locale.Tr "repo.projects.column.delete"}}
|
||||||
data-url="{{$.Link}}/{{.ID}}/unsetdefault">
|
|
||||||
{{svg "octicon-pin-slash"}}
|
|
||||||
{{ctx.Locale.Tr "repo.projects.column.unset_default"}}
|
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<a class="item show-modal button show-delete-project-column-modal"
|
|
||||||
data-modal="#delete-project-column-modal-{{.ID}}"
|
|
||||||
data-url="{{$.Link}}/{{.ID}}">
|
|
||||||
{{svg "octicon-trash"}}
|
|
||||||
{{ctx.Locale.Tr "repo.projects.column.delete"}}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="ui small modal edit-project-column-modal" id="edit-project-column-modal-{{.ID}}">
|
<div class="ui small modal edit-project-column-modal" id="edit-project-column-modal-{{.ID}}">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
@ -165,7 +156,7 @@
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<div class="ui cards {{if and $canWriteProject (ne .ID 0)}}{{/* ID 0 is default column which cannot be moved */}}tw-cursor-grab{{end}}" data-url="{{$.Link}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}">
|
<div class="ui cards{{if $canWriteProject}} tw-cursor-grab{{end}}" data-url="{{$.Link}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}">
|
||||||
{{range (index $.IssuesMap .ID)}}
|
{{range (index $.IssuesMap .ID)}}
|
||||||
<div class="issue-card gt-word-break {{if $canWriteProject}}tw-cursor-grab{{end}}" data-issue="{{.ID}}">
|
<div class="issue-card gt-word-break {{if $canWriteProject}}tw-cursor-grab{{end}}" data-issue="{{.ID}}">
|
||||||
{{template "repo/issue/card" (dict "Issue" . "Page" $)}}
|
{{template "repo/issue/card" (dict "Issue" . "Page" $)}}
|
||||||
|
|
|
@ -58,7 +58,6 @@ async function initRepoProjectSortable() {
|
||||||
createSortable(mainBoard, {
|
createSortable(mainBoard, {
|
||||||
group: 'project-column',
|
group: 'project-column',
|
||||||
draggable: '.project-column',
|
draggable: '.project-column',
|
||||||
filter: '[data-id="0"]',
|
|
||||||
animation: 150,
|
animation: 150,
|
||||||
ghostClass: 'card-ghost',
|
ghostClass: 'card-ghost',
|
||||||
delayOnTouchOnly: true,
|
delayOnTouchOnly: true,
|
||||||
|
|
Loading…
Reference in a new issue