diff --git a/release-notes/4547.md b/release-notes/4547.md new file mode 100644 index 0000000000..08f131fccd --- /dev/null +++ b/release-notes/4547.md @@ -0,0 +1 @@ +The milestone section in the sidebar on the issue and pull request page now uses HTMX. If you update the milestone of a issue or pull request it will no longer reload the whole page and instead update the current page with the new information about the milestone update. This should provide a smoother user experience. diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index e34f90c73b..dcc1cdd467 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -1370,6 +1370,22 @@ func getBranchData(ctx *context.Context, issue *issues_model.Issue) { } } +func prepareHiddenCommentType(ctx *context.Context) { + var hiddenCommentTypes *big.Int + if ctx.IsSigned { + val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes) + if err != nil { + ctx.ServerError("GetUserSetting", err) + return + } + hiddenCommentTypes, _ = new(big.Int).SetString(val, 10) // we can safely ignore the failed conversion here + } + + ctx.Data["ShouldShowCommentType"] = func(commentType issues_model.CommentType) bool { + return hiddenCommentTypes == nil || hiddenCommentTypes.Bit(int(commentType)) == 0 + } +} + // ViewIssue render issue view page func ViewIssue(ctx *context.Context) { if ctx.Params(":type") == "issues" { @@ -2019,21 +2035,13 @@ func ViewIssue(ctx *context.Context) { ctx.Data["NewPinAllowed"] = pinAllowed ctx.Data["PinEnabled"] = setting.Repository.Issue.MaxPinned != 0 - var hiddenCommentTypes *big.Int - if ctx.IsSigned { - val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes) - if err != nil { - ctx.ServerError("GetUserSetting", err) - return - } - hiddenCommentTypes, _ = new(big.Int).SetString(val, 10) // we can safely ignore the failed conversion here - } - ctx.Data["ShouldShowCommentType"] = func(commentType issues_model.CommentType) bool { - return hiddenCommentTypes == nil || hiddenCommentTypes.Bit(int(commentType)) == 0 + prepareHiddenCommentType(ctx) + if ctx.Written() { + return } + // For sidebar PrepareBranchList(ctx) - if ctx.Written() { return } @@ -2342,7 +2350,49 @@ func UpdateIssueMilestone(ctx *context.Context) { } } - ctx.JSONOK() + if ctx.FormBool("htmx") { + renderMilestones(ctx) + if ctx.Written() { + return + } + prepareHiddenCommentType(ctx) + if ctx.Written() { + return + } + + issue := issues[0] + var err error + if issue.MilestoneID > 0 { + issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, issue.MilestoneID) + if err != nil { + ctx.ServerError("GetMilestoneByRepoID", err) + return + } + } else { + issue.Milestone = nil + } + + comment := &issues_model.Comment{} + has, err := db.GetEngine(ctx).Where("issue_id = ? AND type = ?", issue.ID, issues_model.CommentTypeMilestone).OrderBy("id DESC").Limit(1).Get(comment) + if !has || err != nil { + ctx.ServerError("GetLatestMilestoneComment", err) + } + if err := comment.LoadMilestone(ctx); err != nil { + ctx.ServerError("LoadMilestone", err) + return + } + if err := comment.LoadPoster(ctx); err != nil { + ctx.ServerError("LoadPoster", err) + return + } + issue.Comments = issues_model.CommentList{comment} + + ctx.Data["Issue"] = issue + ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) + ctx.HTML(http.StatusOK, "htmx/milestone_sidebar") + } else { + ctx.JSONOK() + } } // UpdateIssueAssignee change issue's or pull's assignee diff --git a/templates/htmx/milestone_sidebar.tmpl b/templates/htmx/milestone_sidebar.tmpl new file mode 100644 index 0000000000..458dabc5b1 --- /dev/null +++ b/templates/htmx/milestone_sidebar.tmpl @@ -0,0 +1,4 @@ +
+ {{template "repo/issue/view_content/comments" .}} +
+{{template "repo/issue/view_content/sidebar/milestones" .}} diff --git a/templates/repo/issue/milestone/select_menu.tmpl b/templates/repo/issue/milestone/select_menu.tmpl index 9b0492ce52..eae2f3baa9 100644 --- a/templates/repo/issue/milestone/select_menu.tmpl +++ b/templates/repo/issue/milestone/select_menu.tmpl @@ -5,7 +5,7 @@
{{end}} -
{{ctx.Locale.Tr "repo.issues.new.clear_milestone"}}
+
{{ctx.Locale.Tr "repo.issues.new.clear_milestone"}}
{{if and (not .OpenMilestones) (not .ClosedMilestones)}}
{{ctx.Locale.Tr "repo.issues.new.no_items"}} @@ -17,7 +17,7 @@ {{ctx.Locale.Tr "repo.issues.new.open_milestone"}}
{{range .OpenMilestones}} - + {{svg "octicon-milestone" 16 "tw-mr-1"}} {{.Name}} @@ -29,7 +29,7 @@ {{ctx.Locale.Tr "repo.issues.new.closed_milestone"}} {{range .ClosedMilestones}} - + {{svg "octicon-milestone" 16 "tw-mr-1"}} {{.Name}} diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 543191e02d..b97ce8266f 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -72,7 +72,8 @@ - {{template "repo/issue/view_content/comments" .}} + {{template "repo/issue/view_content/comments" .}} +
{{if and .Issue.IsPull (not $.Repository.IsArchived)}} {{template "repo/issue/view_content/pull".}} diff --git a/templates/repo/issue/view_content/sidebar/milestones.tmpl b/templates/repo/issue/view_content/sidebar/milestones.tmpl index 661ca80743..44d9419f9b 100644 --- a/templates/repo/issue/view_content/sidebar/milestones.tmpl +++ b/templates/repo/issue/view_content/sidebar/milestones.tmpl @@ -1,22 +1,24 @@ - -
- {{ctx.Locale.Tr "repo.issues.new.no_milestone"}} -
- {{if .Issue.Milestone}} - - {{svg "octicon-milestone" 18 "tw-mr-2"}} - {{.Issue.Milestone.Name}} - - {{end}} + diff --git a/tests/e2e/issue-sidebar.test.e2e.js b/tests/e2e/issue-sidebar.test.e2e.js index 41b1b2064a..4bd211abe5 100644 --- a/tests/e2e/issue-sidebar.test.e2e.js +++ b/tests/e2e/issue-sidebar.test.e2e.js @@ -84,3 +84,27 @@ test('Issue: Labels', async ({browser}, workerInfo) => { await expect(labelList.filter({hasText: 'label2'})).not.toBeVisible(); await expect(labelList.filter({hasText: 'label1'})).toBeVisible(); }); + +test('Issue: Milestone', async ({browser}, workerInfo) => { + test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); + const page = await login({browser}, workerInfo); + + const response = await page.goto('/user2/repo1/issues/1'); + await expect(response?.status()).toBe(200); + + const selectedMilestone = page.locator('.issue-content-right .select-milestone.list'); + const milestoneDropdown = page.locator('.issue-content-right .select-milestone.dropdown'); + await expect(selectedMilestone).toContainText('No milestone'); + + // Add milestone. + await milestoneDropdown.click(); + await page.getByRole('option', {name: 'milestone1'}).click(); + await expect(selectedMilestone).toContainText('milestone1'); + await expect(page.locator('.timeline-item.event').last()).toContainText('user2 added this to the milestone1 milestone'); + + // Clear milestone. + await milestoneDropdown.click(); + await page.getByText('Clear milestone', {exact: true}).click(); + await expect(selectedMilestone).toContainText('No milestone'); + await expect(page.locator('.timeline-item.event').last()).toContainText('user2 removed this from the milestone1 milestone'); +}); diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 76de8daf56..d3566fb121 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -270,9 +270,7 @@ export function initRepoCommentForm() { } let icon = ''; - if (input_id === '#milestone_id') { - icon = svg('octicon-milestone', 18, 'tw-mr-2'); - } else if (input_id === '#project_id') { + if (input_id === '#project_id') { icon = svg('octicon-project', 18, 'tw-mr-2'); } else if (input_id === '#assignee_id') { icon = `avatar`; @@ -313,7 +311,6 @@ export function initRepoCommentForm() { // Milestone, Assignee, Project selectItem('.select-project', '#project_id'); - selectItem('.select-milestone', '#milestone_id'); selectItem('.select-assignee', '#assignee_id'); }