@@ -36,6 +36,7 @@ type ProjectBoard struct { | |||||
ID int64 `xorm:"pk autoincr"` | ID int64 `xorm:"pk autoincr"` | ||||
Title string | Title string | ||||
Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board | Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board | ||||
Sorting int8 `xorm:"DEFAULT 0"` | |||||
ProjectID int64 `xorm:"INDEX NOT NULL"` | ProjectID int64 `xorm:"INDEX NOT NULL"` | ||||
CreatorID int64 `xorm:"NOT NULL"` | CreatorID int64 `xorm:"NOT NULL"` | ||||
@@ -157,15 +158,24 @@ func getProjectBoard(e Engine, boardID int64) (*ProjectBoard, error) { | |||||
return board, nil | return board, nil | ||||
} | } | ||||
// UpdateProjectBoard updates the title of a project board | |||||
// UpdateProjectBoard updates a project board | |||||
func UpdateProjectBoard(board *ProjectBoard) error { | func UpdateProjectBoard(board *ProjectBoard) error { | ||||
return updateProjectBoard(x, board) | return updateProjectBoard(x, board) | ||||
} | } | ||||
func updateProjectBoard(e Engine, board *ProjectBoard) error { | func updateProjectBoard(e Engine, board *ProjectBoard) error { | ||||
_, err := e.ID(board.ID).Cols( | |||||
"title", | |||||
).Update(board) | |||||
var fieldToUpdate []string | |||||
if board.Sorting != 0 { | |||||
fieldToUpdate = append(fieldToUpdate, "sorting") | |||||
} | |||||
if board.Title != "" { | |||||
fieldToUpdate = append(fieldToUpdate, "title") | |||||
} | |||||
_, err := e.ID(board.ID).Cols(fieldToUpdate...).Update(board) | |||||
return err | return err | ||||
} | } | ||||
@@ -178,7 +188,7 @@ func GetProjectBoards(projectID int64) (ProjectBoardList, error) { | |||||
func getProjectBoards(e Engine, projectID int64) ([]*ProjectBoard, error) { | func getProjectBoards(e Engine, projectID int64) ([]*ProjectBoard, error) { | ||||
var boards = make([]*ProjectBoard, 0, 5) | var boards = make([]*ProjectBoard, 0, 5) | ||||
if err := e.Where("project_id=? AND `default`=?", projectID, false).Find(&boards); err != nil { | |||||
if err := e.Where("project_id=? AND `default`=?", projectID, false).OrderBy("Sorting").Find(&boards); err != nil { | |||||
return nil, err | return nil, err | ||||
} | } | ||||
@@ -277,3 +287,17 @@ func (bs ProjectBoardList) LoadIssues() (IssueList, error) { | |||||
} | } | ||||
return issues, nil | return issues, nil | ||||
} | } | ||||
// UpdateProjectBoardSorting update project board sorting | |||||
func UpdateProjectBoardSorting(bs ProjectBoardList) error { | |||||
for i := range bs { | |||||
_, err := x.ID(bs[i].ID).Cols( | |||||
"sorting", | |||||
).Update(bs[i]) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
} | |||||
return nil | |||||
} |
@@ -487,10 +487,10 @@ type UserCreateProjectForm struct { | |||||
UID int64 `binding:"Required"` | UID int64 `binding:"Required"` | ||||
} | } | ||||
// EditProjectBoardTitleForm is a form for editing the title of a project's | |||||
// board | |||||
type EditProjectBoardTitleForm struct { | |||||
Title string `binding:"Required;MaxSize(100)"` | |||||
// EditProjectBoardForm is a form for editing a project board | |||||
type EditProjectBoardForm struct { | |||||
Title string `binding:"Required;MaxSize(100)"` | |||||
Sorting int8 | |||||
} | } | ||||
// _____ .__.__ __ | // _____ .__.__ __ | ||||
@@ -403,7 +403,7 @@ func DeleteProjectBoard(ctx *context.Context) { | |||||
// AddBoardToProjectPost allows a new board to be added to a project. | // AddBoardToProjectPost allows a new board to be added to a project. | ||||
func AddBoardToProjectPost(ctx *context.Context) { | func AddBoardToProjectPost(ctx *context.Context) { | ||||
form := web.GetForm(ctx).(*auth.EditProjectBoardTitleForm) | |||||
form := web.GetForm(ctx).(*auth.EditProjectBoardForm) | |||||
if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(models.AccessModeWrite, models.UnitTypeProjects) { | if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(models.AccessModeWrite, models.UnitTypeProjects) { | ||||
ctx.JSON(403, map[string]string{ | ctx.JSON(403, map[string]string{ | ||||
"message": "Only authorized users are allowed to perform this action.", | "message": "Only authorized users are allowed to perform this action.", | ||||
@@ -481,9 +481,9 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project, | |||||
return project, board | return project, board | ||||
} | } | ||||
// EditProjectBoardTitle allows a project board's title to be updated | |||||
func EditProjectBoardTitle(ctx *context.Context) { | |||||
form := web.GetForm(ctx).(*auth.EditProjectBoardTitleForm) | |||||
// EditProjectBoard allows a project board's to be updated | |||||
func EditProjectBoard(ctx *context.Context) { | |||||
form := web.GetForm(ctx).(*auth.EditProjectBoardForm) | |||||
_, board := checkProjectBoardChangePermissions(ctx) | _, board := checkProjectBoardChangePermissions(ctx) | ||||
if ctx.Written() { | if ctx.Written() { | ||||
return | return | ||||
@@ -493,6 +493,10 @@ func EditProjectBoardTitle(ctx *context.Context) { | |||||
board.Title = form.Title | board.Title = form.Title | ||||
} | } | ||||
if form.Sorting != 0 { | |||||
board.Sorting = form.Sorting | |||||
} | |||||
if err := models.UpdateProjectBoard(board); err != nil { | if err := models.UpdateProjectBoard(board); err != nil { | ||||
ctx.ServerError("UpdateProjectBoard", err) | ctx.ServerError("UpdateProjectBoard", err) | ||||
return | return | ||||
@@ -853,7 +853,7 @@ func RegisterRoutes(m *web.Route) { | |||||
m.Get("/new", repo.NewProject) | m.Get("/new", repo.NewProject) | ||||
m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost) | m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost) | ||||
m.Group("/{id}", func() { | m.Group("/{id}", func() { | ||||
m.Post("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.AddBoardToProjectPost) | |||||
m.Post("", bindIgnErr(auth.EditProjectBoardForm{}), repo.AddBoardToProjectPost) | |||||
m.Post("/delete", repo.DeleteProject) | m.Post("/delete", repo.DeleteProject) | ||||
m.Get("/edit", repo.EditProject) | m.Get("/edit", repo.EditProject) | ||||
@@ -861,7 +861,7 @@ func RegisterRoutes(m *web.Route) { | |||||
m.Post("/{action:open|close}", repo.ChangeProjectStatus) | m.Post("/{action:open|close}", repo.ChangeProjectStatus) | ||||
m.Group("/{boardID}", func() { | m.Group("/{boardID}", func() { | ||||
m.Put("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.EditProjectBoardTitle) | |||||
m.Put("", bindIgnErr(auth.EditProjectBoardForm{}), repo.EditProjectBoard) | |||||
m.Delete("", repo.DeleteProjectBoard) | m.Delete("", repo.DeleteProjectBoard) | ||||
m.Post("/default", repo.SetDefaultProjectBoard) | m.Post("/default", repo.SetDefaultProjectBoard) | ||||
@@ -72,7 +72,7 @@ | |||||
<div class="board"> | <div class="board"> | ||||
{{ range $board := .Boards }} | {{ range $board := .Boards }} | ||||
<div class="ui segment board-column"> | |||||
<div class="ui segment board-column" data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.RepoLink}}/projects/{{$.Project.ID}}/{{.ID}}"> | |||||
<div class="board-column-header"> | <div class="board-column-header"> | ||||
<div class="ui large label board-label">{{.Title}}</div> | <div class="ui large label board-label">{{.Title}}</div> | ||||
{{if and $.CanWriteProjects (not $.Repository.IsArchived) $.PageIsProjects (ne .ID 0)}} | {{if and $.CanWriteProjects (not $.Repository.IsArchived) $.PageIsProjects (ne .ID 0)}} | ||||
@@ -8,6 +8,34 @@ export default async function initProject() { | |||||
const {Sortable} = await import(/* webpackChunkName: "sortable" */'sortablejs'); | const {Sortable} = await import(/* webpackChunkName: "sortable" */'sortablejs'); | ||||
const boardColumns = document.getElementsByClassName('board-column'); | const boardColumns = document.getElementsByClassName('board-column'); | ||||
new Sortable( | |||||
document.getElementsByClassName('board')[0], | |||||
{ | |||||
group: 'board-column', | |||||
draggable: '.board-column', | |||||
animation: 150, | |||||
onSort: () => { | |||||
const board = document.getElementsByClassName('board')[0]; | |||||
const boardColumns = board.getElementsByClassName('board-column'); | |||||
boardColumns.forEach((column, i) => { | |||||
if (parseInt($(column).data('sorting')) !== i) { | |||||
$.ajax({ | |||||
url: $(column).data('url'), | |||||
data: JSON.stringify({sorting: i}), | |||||
headers: { | |||||
'X-Csrf-Token': csrf, | |||||
'X-Remote': true, | |||||
}, | |||||
contentType: 'application/json', | |||||
method: 'PUT', | |||||
}); | |||||
} | |||||
}); | |||||
}, | |||||
}, | |||||
); | |||||
for (const column of boardColumns) { | for (const column of boardColumns) { | ||||
new Sortable( | new Sortable( | ||||
column.getElementsByClassName('board')[0], | column.getElementsByClassName('board')[0], | ||||
@@ -74,6 +102,7 @@ export default async function initProject() { | |||||
window.location.reload(); | window.location.reload(); | ||||
}); | }); | ||||
$('.delete-project-board').each(function () { | $('.delete-project-board').each(function () { | ||||
$(this).click(function (e) { | $(this).click(function (e) { | ||||
e.preventDefault(); | e.preventDefault(); | ||||