From 1d6b6cf020e6fac3a80c431e45f8db0d93d4d5a9 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 31 Mar 2026 01:21:57 +0800 Subject: [PATCH 01/11] fix --- models/issues/issue_project.go | 30 ------- models/project/column.go | 38 ++++---- routers/web/repo/issue_new.go | 10 +-- routers/web/repo/issue_page_meta.go | 42 ++++++++- services/projects/issue.go | 5 +- templates/repo/issue/new_form.tmpl | 2 +- .../repo/issue/sidebar/project_list.tmpl | 2 +- .../repo/issue/view_content/sidebar.tmpl | 2 +- .../features/repo-issue-sidebar-combolist.ts | 89 +++++++++++++------ web_src/js/features/repo-issue-sidebar.ts | 25 +++--- web_src/js/features/repo-issue.ts | 4 +- 11 files changed, 146 insertions(+), 103 deletions(-) diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index 01852447834c7..3bb09363019f9 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -64,36 +64,6 @@ func LoadProjectIssueColumnMap(ctx context.Context, projectID, defaultColumnID i return result, nil } -// LoadIssuesFromColumn load issues assigned to this column -func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *IssuesOptions) (IssueList, error) { - issueList, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) { - o.ProjectColumnID = b.ID - o.ProjectID = b.ProjectID - o.SortType = "project-column-sorting" - })) - if err != nil { - return nil, err - } - - if b.Default { - issues, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) { - o.ProjectColumnID = db.NoConditionID - o.ProjectID = b.ProjectID - o.SortType = "project-column-sorting" - })) - if err != nil { - return nil, err - } - issueList = append(issueList, issues...) - } - - if err := issueList.LoadComments(ctx); err != nil { - return nil, err - } - - return issueList, nil -} - // IssueAssignOrRemoveProject changes the project associated with an issue // If newProjectID is 0, the issue is removed from the project func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error { diff --git a/models/project/column.go b/models/project/column.go index 79f6dfe911448..659f1868a81bc 100644 --- a/models/project/column.go +++ b/models/project/column.go @@ -257,9 +257,11 @@ func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) { return columns, nil } -// getDefaultColumn return default column and ensure only one exists -func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) { +// GetDefaultColumnWithFallback return default column if one exists, otherwise return the first column by sorting and set it as default column +func (p *Project) GetDefaultColumnWithFallback(ctx context.Context) (*Column, error) { var column Column + + // try to find a column "default=true" has, err := db.GetEngine(ctx). Where("project_id=? AND `default` = ?", p.ID, true). Desc("id").Get(&column) @@ -270,14 +272,29 @@ func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) { if has { return &column, nil } + + // try to first the first column by sorting + has, err = db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column) + if err != nil { + return nil, err + } + if has { + column.Default = true + if _, err := db.GetEngine(ctx).ID(column.ID).Cols("`default`").Update(&column); err != nil { + return nil, err + } + return &column, nil + } + return nil, ErrProjectColumnNotExist{ColumnID: 0} } // MustDefaultColumn returns the default column for a project. // If one exists, it is returned // If none exists, the first column will be elevated to the default column of this project +// If there is no column, it creates a default column and returns it func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) { - c, err := p.getDefaultColumn(ctx) + c, err := p.GetDefaultColumnWithFallback(ctx) if err != nil && !IsErrProjectColumnNotExist(err) { return nil, err } @@ -285,21 +302,8 @@ func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) { return c, nil } - var column Column - has, err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column) - if err != nil { - return nil, err - } - if has { - column.Default = true - if _, err := db.GetEngine(ctx).ID(column.ID).Cols("`default`").Update(&column); err != nil { - return nil, err - } - return &column, nil - } - // create a default column if none is found - column = Column{ + column := Column{ ProjectID: p.ID, Default: true, Title: "Uncategorized", diff --git a/routers/web/repo/issue_new.go b/routers/web/repo/issue_new.go index 1393f62fa531a..98fb842ddf7ff 100644 --- a/routers/web/repo/issue_new.go +++ b/routers/web/repo/issue_new.go @@ -121,11 +121,9 @@ func NewIssue(ctx *context.Context) { } pageMetaData.MilestonesData.SelectedMilestoneID = ctx.FormInt64("milestone") - pageMetaData.ProjectsData.SelectedProjectID = ctx.FormInt64("project") - if pageMetaData.ProjectsData.SelectedProjectID > 0 { - if len(ctx.Req.URL.Query().Get("project")) > 0 { - ctx.Data["redirect_after_creation"] = "project" - } + pageMetaData.ProjectsData.SelectedProjectIDs, _ = base.StringsToInt64s(strings.Split(ctx.FormString("project"), ",")) + if len(pageMetaData.ProjectsData.SelectedProjectIDs) == 1 { + ctx.Data["redirect_after_creation"] = "project" } tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) @@ -273,7 +271,7 @@ func ValidateRepoMetasForNewIssue(ctx *context.Context, form forms.CreateIssueFo ctx.NotFound(nil) return ret } - pageMetaData.ProjectsData.SelectedProjectID = form.ProjectID + pageMetaData.ProjectsData.SelectedProjectIDs = util.Iif(form.ProjectID > 0, []int64{form.ProjectID}, nil) // prepare assignees candidateAssignees := toSet(pageMetaData.AssigneesData.CandidateAssignees, func(user *user_model.User) int64 { return user.ID }) diff --git a/routers/web/repo/issue_page_meta.go b/routers/web/repo/issue_page_meta.go index 719b485bc5055..639333ab42599 100644 --- a/routers/web/repo/issue_page_meta.go +++ b/routers/web/repo/issue_page_meta.go @@ -34,9 +34,14 @@ type issueSidebarAssigneesData struct { } type issueSidebarProjectsData struct { - SelectedProjectID int64 - OpenProjects []*project_model.Project - ClosedProjects []*project_model.Project + SelectedProjectIDs []int64 // TODO: support multiple projects in the future + + // the "selected" fields are only valid when len(SelectedProjectIDs)==1 + SelectedProjectColumns []*project_model.Column + SelectedProjectColumn *project_model.Column + + OpenProjects []*project_model.Project + ClosedProjects []*project_model.Project } type IssuePageMetaData struct { @@ -92,6 +97,11 @@ func retrieveRepoIssueMetaData(ctx *context.Context, repo *repo_model.Repository return data } + data.retrieveProjectData(ctx) + if ctx.Written() { + return data + } + // TODO: the issue/pull permissions are quite complex and unclear // A reader could create an issue/PR with setting some meta (eg: assignees from issue template, reviewers, target branch) // A reader(creator) could update some meta (eg: target branch), but can't change assignees anymore. @@ -158,9 +168,33 @@ func (d *IssuePageMetaData) retrieveAssigneesData(ctx *context.Context) { ctx.Data["Assignees"] = d.AssigneesData.CandidateAssignees } +func (d *IssuePageMetaData) retrieveProjectData(ctx *context.Context) { + if d.Issue == nil || d.Issue.Project == nil { + return + } + d.ProjectsData.SelectedProjectIDs = []int64{d.Issue.Project.ID} + columns, err := d.Issue.Project.GetColumns(ctx) + if err != nil { + ctx.ServerError("GetProjectColumns", err) + return + } + d.ProjectsData.SelectedProjectColumns = columns + columnID, err := d.Issue.ProjectColumnID(ctx) + if err != nil { + ctx.ServerError("ProjectColumnID", err) + return + } + for _, col := range columns { + if col.ID == columnID { + d.ProjectsData.SelectedProjectColumn = col + break + } + } +} + func (d *IssuePageMetaData) retrieveProjectsDataForIssueWriter(ctx *context.Context) { if d.Issue != nil && d.Issue.Project != nil { - d.ProjectsData.SelectedProjectID = d.Issue.Project.ID + d.ProjectsData.SelectedProjectIDs = []int64{d.Issue.Project.ID} } d.ProjectsData.OpenProjects, d.ProjectsData.ClosedProjects = retrieveProjectsInternal(ctx, ctx.Repo.Repository) } diff --git a/services/projects/issue.go b/services/projects/issue.go index 590fe960d5329..f25323281a67a 100644 --- a/services/projects/issue.go +++ b/services/projects/issue.go @@ -12,6 +12,7 @@ import ( project_model "code.gitea.io/gitea/models/project" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/util" ) // MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column @@ -100,9 +101,9 @@ func LoadIssuesFromProject(ctx context.Context, project *project_model.Project, return nil, err } - defaultColumn, err := project.MustDefaultColumn(ctx) + defaultColumn, err := project.GetDefaultColumnWithFallback(ctx) if err != nil { - return nil, err + return nil, util.Iif(errors.Is(err, util.ErrNotExist), nil, err) } issueColumnMap, err := issues_model.LoadProjectIssueColumnMap(ctx, project.ID, defaultColumn.ID) diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 5475224750e5d..7d669fe4a71cf 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -46,7 +46,7 @@ -
+
{{template "repo/issue/branch_selector_field" $}}{{/* TODO: RemoveIssueRef: template "repo/issue/branch_selector_field" $*/}} {{if .PageIsComparePull}} diff --git a/templates/repo/issue/sidebar/project_list.tmpl b/templates/repo/issue/sidebar/project_list.tmpl index be8097fbbcabb..ca3b6bdd7ab34 100644 --- a/templates/repo/issue/sidebar/project_list.tmpl +++ b/templates/repo/issue/sidebar/project_list.tmpl @@ -5,7 +5,7 @@
- +