diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 1c332a8337..c3f2e899c2 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -1412,6 +1412,7 @@ "repo.issues.new.open_projects": "Open Projects", "repo.issues.new.closed_projects": "Closed Projects", "repo.issues.new.no_items": "No items", + "repo.issues.custom_fields": "Custom Fields", "repo.issues.new.milestone": "Milestone", "repo.issues.new.no_milestone": "No Milestone", "repo.issues.new.clear_milestone": "Clear milestone", diff --git a/routers/web/repo/issue_custom_field.go b/routers/web/repo/issue_custom_field.go new file mode 100644 index 0000000000..d9345f9c65 --- /dev/null +++ b/routers/web/repo/issue_custom_field.go @@ -0,0 +1,33 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package repo + +import ( + "fmt" + "net/http" + + issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context" +) + +// UpdateIssueCustomField handles POST to set a custom field value on an issue. +func UpdateIssueCustomField(ctx *context.Context) { + issueID := ctx.PathParamInt64("id") + fieldID := ctx.PathParamInt64("field_id") + value := ctx.FormString("value") + + // Look up issue to get the index for redirect. + issue, err := issues_model.GetIssueByID(ctx, issueID) + if err != nil { + ctx.ServerError("GetIssueByID", err) + return + } + + if err := issues_model.SetCustomFieldValue(ctx, issueID, fieldID, value); err != nil { + ctx.ServerError("SetCustomFieldValue", err) + return + } + + ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index), http.StatusSeeOther) +} diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 7ab1be62f0..dfa2a8b34e 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -4,6 +4,7 @@ package repo import ( + "encoding/json" "fmt" "math/big" "net/http" @@ -337,6 +338,25 @@ func ViewIssue(ctx *context.Context) { ctx.Data["IsProjectsEnabled"] = ctx.Repo.Permission.CanRead(unit.TypeProjects) ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled + + // Load custom fields for the issue sidebar. + customFieldDefs, _ := issues_model.GetCustomFieldsByRepo(ctx, ctx.Repo.Repository.ID) + ctx.Data["CustomFieldDefs"] = customFieldDefs + customFieldValues := make(map[int64]string) + fieldOptions := make(map[int64][]string) + if len(customFieldDefs) > 0 { + customFieldValues, _ = issues_model.GetCustomFieldValuesMap(ctx, issue.ID) + for _, f := range customFieldDefs { + if f.Options != "" { + var opts []string + if err := json.Unmarshal([]byte(f.Options), &opts); err == nil { + fieldOptions[f.ID] = opts + } + } + } + } + ctx.Data["CustomFieldValues"] = customFieldValues + ctx.Data["CustomFieldOptions"] = fieldOptions upload.AddUploadContext(ctx, "comment") if err := issue.LoadAttributes(ctx); err != nil { diff --git a/routers/web/web.go b/routers/web/web.go index 4a8ecc18ae..745d4d34e0 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1397,6 +1397,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { m.Post("/projects/column", reqRepoIssuesOrPullsWriter, reqRepoProjectsWriter, repo.UpdateIssueProjectColumn) m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee) m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus) + m.Post("/{id}/custom-fields/{field_id}", reqRepoIssuesOrPullsWriter, repo.UpdateIssueCustomField) m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues) m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin) m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove) diff --git a/templates/repo/issue/sidebar/custom_fields.tmpl b/templates/repo/issue/sidebar/custom_fields.tmpl new file mode 100644 index 0000000000..99757434c4 --- /dev/null +++ b/templates/repo/issue/sidebar/custom_fields.tmpl @@ -0,0 +1,33 @@ +{{if .CustomFieldDefs}} +
+
+ {{$issueID := .Issue.ID}} + {{$repoLink := .RepoLink}} + {{$canModify := .HasIssuesOrPullsWritePermission}} + {{$values := .CustomFieldValues}} + {{$fieldOptions := .CustomFieldOptions}} + {{range .CustomFieldDefs}} + {{$currentVal := index $values .ID}} +
+ {{.Name}} + {{if and $canModify (eq .Options "")}} + {{/* Non-dropdown: just display the value */}} + {{if $currentVal}}{{$currentVal}}{{else}}{{end}} + {{else if $canModify}} +
+ {{$.CsrfTokenHtml}} + +
+ {{else}} + {{if $currentVal}}{{$currentVal}}{{else}}{{end}} + {{end}} +
+ {{end}} +
+{{end}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 6427d10968..9f064ce6ee 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -7,6 +7,8 @@ {{template "repo/issue/sidebar/label_list" $.IssuePageMetaData}} + {{template "repo/issue/sidebar/custom_fields" $}} + {{template "repo/issue/sidebar/milestone_list" $.IssuePageMetaData}} {{if .IsProjectsEnabled}} {{template "repo/issue/sidebar/project_list" $.IssuePageMetaData}}