From 6524bd51caf7b42cdf97f4e600c427fb93764db2 Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Sat, 20 Apr 2024 11:12:22 +0000 Subject: [PATCH 1/8] add project board choice option on issue sidebar Signed-off-by: a1012112796 <1012112796@qq.com> --- models/issues/issue.go | 6 ++ models/issues/issue_project.go | 17 +++++ models/project/board.go | 19 +++++- models/project/issue.go | 68 ++++++++++++++++++- options/locale/locale_en-US.ini | 1 + routers/web/repo/issue.go | 11 +++ routers/web/repo/projects.go | 66 ++++++++++++++++++ routers/web/web.go | 1 + .../repo/issue/view_content/sidebar.tmpl | 16 ++++- web_src/js/features/repo-issue.js | 26 +++++++ web_src/js/features/repo-legacy.js | 6 +- 11 files changed, 229 insertions(+), 8 deletions(-) diff --git a/models/issues/issue.go b/models/issues/issue.go index 87c1c86eb15be..74c5be1db3306 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -140,6 +140,8 @@ type Issue struct { // For view issue page. ShowRole RoleDescriptor `xorm:"-"` + + ProjectIssue *project_model.ProjectIssue `xorm:"-"` } var ( @@ -315,6 +317,10 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) { return err } + if err = issue.LoadProjectIssue(ctx); err != nil { + return err + } + if err = issue.LoadAssignees(ctx); err != nil { return err } diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index 907a5a17b9f20..9f72e10cd1aef 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -28,6 +28,23 @@ func (issue *Issue) LoadProject(ctx context.Context) (err error) { 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 { var ip project_model.ProjectIssue has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip) diff --git a/models/project/board.go b/models/project/board.go index 5f142a356c68d..463eb96da939a 100644 --- a/models/project/board.go +++ b/models/project/board.go @@ -69,7 +69,7 @@ func (Board) TableName() string { } // NumIssues return counter of all issues assigned to the board -func (b *Board) NumIssues(ctx context.Context) int { +func (b *Board) NumIssues(ctx context.Context) (int64, error) { c, err := db.GetEngine(ctx).Table("project_issue"). Where("project_id=?", b.ProjectID). And("project_board_id=?", b.ID). @@ -77,9 +77,9 @@ func (b *Board) NumIssues(ctx context.Context) int { Cols("issue_id"). Count() if err != nil { - return 0 + return 0, err } - return int(c) + return c, nil } func init() { @@ -219,6 +219,19 @@ func GetBoard(ctx context.Context, boardID int64) (*Board, error) { return board, nil } +// GetBoard fetches the current default board of a project +func GetDefaultBoard(ctx context.Context, projectID int64) (*Board, error) { + board := new(Board) + has, err := db.GetEngine(ctx).Where("project_id = ? AND `default` = ?", projectID, true).Get(board) + if err != nil { + return nil, err + } else if !has { + return nil, ErrProjectBoardNotExist{BoardID: -1} + } + + return board, nil +} + // UpdateBoard updates a project board func UpdateBoard(ctx context.Context, board *Board) error { var fieldToUpdate []string diff --git a/models/project/issue.go b/models/project/issue.go index ebc9719de55d0..7c60cee04bdcc 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -18,7 +18,8 @@ type ProjectIssue struct { //revive:disable-line:exported 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 Sorting int64 `xorm:"NOT NULL DEFAULT 0"` @@ -33,6 +34,50 @@ func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error 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 + + if issue.ProjectBoardID == 0 { + issue.ProjectBoard, err = GetDefaultBoard(ctx, issue.ProjectID) + return err + } + + issue.ProjectBoard, err = GetBoard(ctx, issue.ProjectBoardID) + return err +} + // NumIssues return counter of all issues assigned to a project func (p *Project) NumIssues(ctx context.Context) int { c, err := db.GetEngine(ctx).Table("project_issue"). @@ -102,6 +147,27 @@ func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs }) } +func MoveIssueToBoardTail(ctx context.Context, issue *ProjectIssue, toBoard *Board) error { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + num, err := toBoard.NumIssues(ctx) + if err != nil { + return err + } + + _, err = db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", + toBoard.ID, num, issue.IssueID) + if err != nil { + return err + } + + return committer.Commit() +} + 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) return err diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c602aba53dc5f..c6263c37885ff 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1749,6 +1749,7 @@ issues.content_history.delete_from_history = Delete from history issues.content_history.delete_from_history_confirm = Delete from history? issues.content_history.options = Options issues.reference_link = Reference: %s +issues.move_project_boad = Status compare.compare_base = base compare.compare_head = compare diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 1364d7567661d..7a39830d64a4f 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2044,6 +2044,17 @@ func ViewIssue(ctx *context.Context) { return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee) } + canWriteProjects := ctx.Repo.Permission.CanWrite(unit.TypeProjects) + ctx.Data["CanWriteProjects"] = canWriteProjects + + if canWriteProjects && issue.Project != nil { + ctx.Data["ProjectBoards"], err = issue.Project.GetBoards(ctx) + if err != nil { + ctx.ServerError("Project.GetBoards", err) + return + } + } + ctx.HTML(http.StatusOK, tplIssueView) } diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 9b765e89e877f..39e4925db89a2 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -574,6 +574,72 @@ func SetDefaultProjectBoard(ctx *context.Context) { ctx.JSONOK() } +// MoveBoardForIssue move a issue to other board +func MoveBoardForIssue(ctx *context.Context) { + if ctx.Doer == nil { + ctx.JSON(http.StatusForbidden, map[string]string{ + "message": "Only signed in users are allowed to perform this action.", + }) + return + } + + if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(perm.AccessModeWrite, unit.TypeProjects) { + ctx.JSON(http.StatusForbidden, map[string]string{ + "message": "Only authorized users are allowed to perform this action.", + }) + return + } + + issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if issues_model.IsErrIssueNotExist(err) { + ctx.NotFound("GetIssueByIndex", err) + } else { + ctx.ServerError("GetIssueByIndex", err) + } + return + } + + if err := issue.LoadProject(ctx); err != nil { + ctx.ServerError("LoadProject", err) + return + } + if issue.Project == nil { + ctx.NotFound("Project not found", nil) + return + } + + if err = issue.LoadProjectIssue(ctx); err != nil { + ctx.ServerError("LoadProjectIssue", err) + return + } + + 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 != issue.Project.ID { + ctx.NotFound("BoardNotInProject", nil) + return + } + + err = project_model.MoveIssueToBoardTail(ctx, issue.ProjectIssue, board) + if err != nil { + ctx.NotFound("MoveIssueToBoardTail", nil) + return + } + + issue.Repo = ctx.Repo.Repository + + ctx.JSONRedirect(issue.HTMLURL()) +} + // MoveIssues moves or keeps issues in a column and sorts them inside that column func MoveIssues(ctx *context.Context) { if ctx.Doer == nil { diff --git a/routers/web/web.go b/routers/web/web.go index 8fa24a28244e5..e056418fe4d40 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1204,6 +1204,7 @@ func registerRoutes(m *web.Route) { m.Post("/lock", reqRepoIssuesOrPullsWriter, web.Bind(forms.IssueLockForm{}), repo.LockIssue) m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue) m.Post("/delete", reqRepoAdmin, repo.DeleteIssue) + m.Post("/move_project_board/{boardID}", repo.MoveBoardForIssue) }, context.RepoMustNotBeArchived()) m.Group("/{index}", func() { diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index bb0bb2cff3602..d65fd12b3533e 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -193,13 +193,25 @@ {{end}} -
+
{{ctx.Locale.Tr "repo.issues.new.no_projects"}}
{{if .Issue.Project}} - + {{svg .Issue.Project.IconName 18 "tw-mr-2"}}{{.Issue.Project.Title}} + {{end}}
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 2b2eed58bbfb3..699fe24051b92 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -746,3 +746,29 @@ export function initArchivedLabelHandler() { toggleElem(label, label.classList.contains('checked')); } } + +export function initIssueProjectBoardSelector() { + const root = document.querySelector('.select-issue-project-board'); + if (!root) return; + + const link = root.getAttribute('data-url'); + + for (const board of document.querySelectorAll('.select-issue-project-board .item')) { + board.addEventListener('click', async (e) => { + e.preventDefault(); + e.stopImmediatePropagation(); + + try { + const response = await POST(`${link}${board.getAttribute('data-board-id')}`); + if (response.ok) { + const data = await response.json(); + window.location.href = data.redirect; + } + } catch (error) { + console.error(error); + } + + return false; + }); + } +} diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 18d98c891d066..ec478c125d082 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -4,6 +4,7 @@ import { initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueReferenceIssue, initRepoIssueTitleEdit, initRepoIssueWipToggle, initRepoPullRequestUpdate, updateIssuesMeta, initIssueTemplateCommentEditors, initSingleCommentEditor, + initIssueProjectBoardSelector, } from './repo-issue.js'; import {initUnicodeEscapeButton} from './repo-unicode-escape.js'; import {svg} from '../svg.js'; @@ -182,7 +183,7 @@ export function initRepoCommentForm() { // TODO: Which thing should be done for choosing review requests // to make chosen items be shown on time here? - if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { + if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify' || selector === 'select-issue-project-board') { return false; } @@ -222,7 +223,7 @@ export function initRepoCommentForm() { $(this).find('.octicon-check').addClass('tw-invisible'); }); - if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { + if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify' || selector === 'select-issue-project-board') { return false; } @@ -394,6 +395,7 @@ export function initRepository() { initRepoIssueCodeCommentCancel(); initRepoPullRequestUpdate(); initCompReactionSelector(); + initIssueProjectBoardSelector(); initRepoPullRequestMergeForm(); initRepoPullRequestCommitStatus(); From 64701bdc42457ed96095254d9d3d4b6a0299f01e Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Mon, 22 Apr 2024 03:36:11 +0000 Subject: [PATCH 2/8] fix lint Signed-off-by: a1012112796 <1012112796@qq.com> --- .../repo/issue/view_content/sidebar.tmpl | 26 +++++++++---------- web_src/js/features/repo-legacy.js | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index d65fd12b3533e..473167becd632 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -193,25 +193,25 @@ {{end}}
-
+
{{ctx.Locale.Tr "repo.issues.new.no_projects"}}
{{if .Issue.Project}} {{svg .Issue.Project.IconName 18 "tw-mr-2"}}{{.Issue.Project.Title}} - + {{end}}
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index ec478c125d082..1dc67ded2d955 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -183,7 +183,7 @@ export function initRepoCommentForm() { // TODO: Which thing should be done for choosing review requests // to make chosen items be shown on time here? - if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify' || selector === 'select-issue-project-board') { + if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { return false; } @@ -223,7 +223,7 @@ export function initRepoCommentForm() { $(this).find('.octicon-check').addClass('tw-invisible'); }); - if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify' || selector === 'select-issue-project-board') { + if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { return false; } From 9207a4e7bf340825dac048f3b245f91ae27627a8 Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Tue, 23 Apr 2024 10:11:38 +0000 Subject: [PATCH 3/8] remove not used code Signed-off-by: a1012112796 <1012112796@qq.com> --- models/project/board.go | 13 ------------- models/project/issue.go | 5 ----- 2 files changed, 18 deletions(-) diff --git a/models/project/board.go b/models/project/board.go index 45892b88c11cf..b294f2f63401e 100644 --- a/models/project/board.go +++ b/models/project/board.go @@ -217,19 +217,6 @@ func GetBoard(ctx context.Context, boardID int64) (*Board, error) { return board, nil } -// GetBoard fetches the current default board of a project -func GetDefaultBoard(ctx context.Context, projectID int64) (*Board, error) { - board := new(Board) - has, err := db.GetEngine(ctx).Where("project_id = ? AND `default` = ?", projectID, true).Get(board) - if err != nil { - return nil, err - } else if !has { - return nil, ErrProjectBoardNotExist{BoardID: -1} - } - - return board, nil -} - // UpdateBoard updates a project board func UpdateBoard(ctx context.Context, board *Board) error { var fieldToUpdate []string diff --git a/models/project/issue.go b/models/project/issue.go index 7c60cee04bdcc..51468c1792f82 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -69,11 +69,6 @@ func (issue *ProjectIssue) LoadProjectBoard(ctx context.Context) error { var err error - if issue.ProjectBoardID == 0 { - issue.ProjectBoard, err = GetDefaultBoard(ctx, issue.ProjectID) - return err - } - issue.ProjectBoard, err = GetBoard(ctx, issue.ProjectBoardID) return err } From 85b93b24385863afeebd2f0763380533aaeac36d Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Mon, 3 Jun 2024 01:22:46 +0000 Subject: [PATCH 4/8] fix nits Signed-off-by: a1012112796 <1012112796@qq.com> --- models/issues/issue_project.go | 2 +- models/project/issue.go | 15 ++++++++------- routers/web/repo/issue.go | 2 +- routers/web/repo/projects.go | 14 +++++++------- routers/web/web.go | 2 +- templates/repo/issue/view_content/sidebar.tmpl | 8 ++++---- 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index fbfaef176a824..6331b668fab8f 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -42,7 +42,7 @@ func (issue *Issue) LoadProjectIssue(ctx context.Context) (err error) { return err } - return issue.ProjectIssue.LoadProjectBoard(ctx) + return issue.ProjectIssue.LoadProjectColumn(ctx) } func (issue *Issue) projectID(ctx context.Context) int64 { diff --git a/models/project/issue.go b/models/project/issue.go index 4c97243f037ef..b0372cde31ae5 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -19,7 +19,8 @@ type ProjectIssue struct { //revive:disable-line:exported ProjectID int64 `xorm:"INDEX"` // ProjectColumnID should not be zero since 1.22. If it's zero, the issue will not be displayed on UI and it might result in errors. - ProjectColumnID int64 `xorm:"'project_board_id' INDEX"` + ProjectColumnID int64 `xorm:"'project_board_id' INDEX"` + ProjectColumn *Column `xorm:"-"` // the sorting order on the column Sorting int64 `xorm:"NOT NULL DEFAULT 0"` @@ -62,14 +63,14 @@ func GetProjectIssueByIssueID(ctx context.Context, issueID int64) (*ProjectIssue return issue, nil } -func (issue *ProjectIssue) LoadProjectBoard(ctx context.Context) error { - if issue.ProjectBoard != nil { +func (issue *ProjectIssue) LoadProjectColumn(ctx context.Context) error { + if issue.ProjectColumn != nil { return nil } var err error - issue.ProjectBoard, err = GetBoard(ctx, issue.ProjectBoardID) + issue.ProjectColumn, err = GetColumn(ctx, issue.ProjectColumnID) return err } @@ -139,20 +140,20 @@ func MoveIssuesOnProjectColumn(ctx context.Context, column *Column, sortedIssueI }) } -func MoveIssueToBoardTail(ctx context.Context, issue *ProjectIssue, toBoard *Board) error { +func MoveIssueToColumnTail(ctx context.Context, issue *ProjectIssue, toColumn *Column) error { ctx, committer, err := db.TxContext(ctx) if err != nil { return err } defer committer.Close() - num, err := toBoard.NumIssues(ctx) + num, err := toColumn.NumIssues(ctx) if err != nil { return err } _, err = db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", - toBoard.ID, num, issue.IssueID) + toColumn.ID, num, issue.IssueID) if err != nil { return err } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 6b1fc7d3736fc..71f876c85dd48 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2050,7 +2050,7 @@ func ViewIssue(ctx *context.Context) { ctx.Data["CanWriteProjects"] = canWriteProjects if canWriteProjects && issue.Project != nil { - ctx.Data["ProjectBoards"], err = issue.Project.GetBoards(ctx) + ctx.Data["ProjectColumns"], err = issue.Project.GetColumns(ctx) if err != nil { ctx.ServerError("Project.GetBoards", err) return diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index cff440e9243ee..50ab6b9afd969 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -619,22 +619,22 @@ func MoveBoardForIssue(ctx *context.Context) { return } - board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) + column, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":columnID")) if err != nil { - if project_model.IsErrProjectBoardNotExist(err) { - ctx.NotFound("ProjectBoardNotExist", nil) + if project_model.IsErrProjectColumnNotExist(err) { + ctx.NotFound("ErrProjectColumnNotExist", nil) } else { - ctx.ServerError("GetProjectBoard", err) + ctx.ServerError("GetColumn", err) } return } - if board.ProjectID != issue.Project.ID { - ctx.NotFound("BoardNotInProject", nil) + if column.ProjectID != issue.Project.ID { + ctx.NotFound("ColumnNotInProject", nil) return } - err = project_model.MoveIssueToBoardTail(ctx, issue.ProjectIssue, board) + err = project_model.MoveIssueToColumnTail(ctx, issue.ProjectIssue, column) if err != nil { ctx.NotFound("MoveIssueToBoardTail", nil) return diff --git a/routers/web/web.go b/routers/web/web.go index 255b19005a31e..1fc063fdf180b 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1212,7 +1212,7 @@ func registerRoutes(m *web.Route) { m.Post("/lock", reqRepoIssuesOrPullsWriter, web.Bind(forms.IssueLockForm{}), repo.LockIssue) m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue) m.Post("/delete", reqRepoAdmin, repo.DeleteIssue) - m.Post("/move_project_board/{boardID}", repo.MoveBoardForIssue) + m.Post("/move_project_column/{columnID}", repo.MoveBoardForIssue) }, context.RepoMustNotBeArchived()) m.Group("/{index}", func() { diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 473167becd632..6bedc7fd93c96 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -200,13 +200,13 @@ {{svg .Issue.Project.IconName 18 "tw-mr-2"}}{{.Issue.Project.Title}} -