diff --git a/models/project_board.go b/models/project_board.go index a9c0b3ed8..e56bf8f81 100644 --- a/models/project_board.go +++ b/models/project_board.go @@ -36,6 +36,7 @@ type ProjectBoard struct { ID int64 `xorm:"pk autoincr"` Title string 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"` CreatorID int64 `xorm:"NOT NULL"` @@ -157,15 +158,24 @@ func getProjectBoard(e Engine, boardID int64) (*ProjectBoard, error) { return board, nil } -// UpdateProjectBoard updates the title of a project board +// UpdateProjectBoard updates a project board func UpdateProjectBoard(board *ProjectBoard) error { return updateProjectBoard(x, board) } 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 } @@ -178,7 +188,7 @@ func GetProjectBoards(projectID int64) (ProjectBoardList, error) { func getProjectBoards(e Engine, projectID int64) ([]*ProjectBoard, error) { 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 } @@ -277,3 +287,17 @@ func (bs ProjectBoardList) LoadIssues() (IssueList, error) { } 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 +} diff --git a/modules/forms/repo_form.go b/modules/forms/repo_form.go index ac968a1dd..f177b21f0 100644 --- a/modules/forms/repo_form.go +++ b/modules/forms/repo_form.go @@ -487,10 +487,10 @@ type UserCreateProjectForm struct { 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 } // _____ .__.__ __ diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 49bcfef0c..4aa03e9ef 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -403,7 +403,7 @@ func DeleteProjectBoard(ctx *context.Context) { // AddBoardToProjectPost allows a new board to be added to a project. 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) { ctx.JSON(403, map[string]string{ "message": "Only authorized users are allowed to perform this action.", @@ -481,9 +481,9 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project, 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) if ctx.Written() { return @@ -493,6 +493,10 @@ func EditProjectBoardTitle(ctx *context.Context) { board.Title = form.Title } + if form.Sorting != 0 { + board.Sorting = form.Sorting + } + if err := models.UpdateProjectBoard(board); err != nil { ctx.ServerError("UpdateProjectBoard", err) return diff --git a/routers/routes/web.go b/routers/routes/web.go index 1f860a623..9e3e690fb 100644 --- a/routers/routes/web.go +++ b/routers/routes/web.go @@ -853,7 +853,7 @@ func RegisterRoutes(m *web.Route) { m.Get("/new", repo.NewProject) m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost) m.Group("/{id}", func() { - m.Post("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.AddBoardToProjectPost) + m.Post("", bindIgnErr(auth.EditProjectBoardForm{}), repo.AddBoardToProjectPost) m.Post("/delete", repo.DeleteProject) m.Get("/edit", repo.EditProject) @@ -861,7 +861,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/{action:open|close}", repo.ChangeProjectStatus) m.Group("/{boardID}", func() { - m.Put("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.EditProjectBoardTitle) + m.Put("", bindIgnErr(auth.EditProjectBoardForm{}), repo.EditProjectBoard) m.Delete("", repo.DeleteProjectBoard) m.Post("/default", repo.SetDefaultProjectBoard) diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl index 9b2aa4bc7..de1fc37b0 100644 --- a/templates/repo/projects/view.tmpl +++ b/templates/repo/projects/view.tmpl @@ -72,7 +72,7 @@
{{ range $board := .Boards }} -
+
{{.Title}}
{{if and $.CanWriteProjects (not $.Repository.IsArchived) $.PageIsProjects (ne .ID 0)}} diff --git a/web_src/js/features/projects.js b/web_src/js/features/projects.js index b5f52f744..254079b76 100644 --- a/web_src/js/features/projects.js +++ b/web_src/js/features/projects.js @@ -8,6 +8,34 @@ export default async function initProject() { const {Sortable} = await import(/* webpackChunkName: "sortable" */'sortablejs'); 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) { new Sortable( column.getElementsByClassName('board')[0], @@ -74,6 +102,7 @@ export default async function initProject() { window.location.reload(); }); + $('.delete-project-board').each(function () { $(this).click(function (e) { e.preventDefault();