mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
Reverts #1126, #1125, #1124, #1123 and #1122. `go.mod` keeps the Go 1.26 line with toolchain `go1.26.9` (instead of the original `go1.26.6`) so the security fix from #1131 isn't lost. reopens #69 reopens #141 reopens #162 reopens #172 reopens #197 reopens #232 reopens #240 reopens #246 reopens #323 reopens #325 reopens #326 reopens #328 reopens #344 reopens #351 reopens #373 reopens #403 reopens #406 reopens #417 reopens #430 reopens #457 reopens #480 reopens #482 reopens #489 reopens #490 reopens #498 reopens #500 reopens #504 reopens #510 reopens #516 reopens #523 reopens #556 reopens #560 reopens #569 reopens #571 reopens #602 reopens #632 reopens #633 reopens #643 reopens #666 reopens #670 reopens #672 reopens #677 reopens #683 reopens #697 reopens #726 reopens #737 reopens #770 reopens #779 reopens #804 reopens #817 reopens #831 reopens #833 reopens #834 reopens #908 reopens #966 reopens #1023 reopens #1029 reopens #1032 reopens #1050 reopens #1052 reopens #1069 reopens #1073 reopens #1087 reopens #1091 reopens #1094 reopens #1110 reopens #1115 reopens #1116 reopens #1118 Reviewed-on: https://gitea.com/gitea/tea/pulls/1146 Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
194 lines
4.6 KiB
Go
194 lines
4.6 KiB
Go
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package interact
|
|
|
|
import (
|
|
stdctx "context"
|
|
"slices"
|
|
"strings"
|
|
|
|
gitea "gitea.dev/sdk"
|
|
"gitea.dev/tea/modules/config"
|
|
"gitea.dev/tea/modules/context"
|
|
"gitea.dev/tea/modules/task"
|
|
"gitea.dev/tea/modules/theme"
|
|
|
|
"charm.land/huh/v2"
|
|
)
|
|
|
|
// EditIssue interactively edits an issue
|
|
func EditIssue(requestCtx stdctx.Context, ctx context.TeaContext, index int64) (*task.EditIssueOption, error) {
|
|
opts := task.EditIssueOption{}
|
|
var err error
|
|
|
|
ctx.Owner, ctx.Repo, err = promptRepoSlug(ctx.Owner, ctx.Repo)
|
|
if err != nil {
|
|
return &opts, err
|
|
}
|
|
printTitleAndContent("Target repo:", ctx.Owner+"/"+ctx.Repo)
|
|
|
|
c := ctx.Login.Client()
|
|
i, _, err := c.Issues.GetIssue(requestCtx, ctx.Owner, ctx.Repo, index)
|
|
if err != nil {
|
|
return &opts, err
|
|
}
|
|
|
|
opts = task.EditIssueOption{
|
|
Index: index,
|
|
Title: &i.Title,
|
|
Body: &i.Body,
|
|
Deadline: i.Deadline,
|
|
}
|
|
|
|
i.Assignees = cleanAssignees(i.Assignees)
|
|
if len(i.Assignees) != 0 {
|
|
for _, a := range i.Assignees {
|
|
opts.SetAssignees = append(opts.SetAssignees, a.UserName)
|
|
}
|
|
}
|
|
|
|
if len(i.Labels) != 0 {
|
|
for _, l := range i.Labels {
|
|
opts.AddLabels = append(opts.AddLabels, l.Name)
|
|
}
|
|
}
|
|
|
|
if i.Milestone != nil {
|
|
opts.Milestone = &i.Milestone.Title
|
|
}
|
|
|
|
if err := promptIssueEditProperties(requestCtx, &ctx, &opts); err != nil {
|
|
return &opts, err
|
|
}
|
|
|
|
return &opts, err
|
|
}
|
|
|
|
func promptIssueEditProperties(requestCtx stdctx.Context, ctx *context.TeaContext, o *task.EditIssueOption) error {
|
|
var milestoneName string
|
|
var labelsSelected []string
|
|
var err error
|
|
|
|
selectableChan := make(chan (issueSelectables), 1)
|
|
go fetchIssueSelectables(requestCtx, ctx.Login, ctx.Owner, ctx.Repo, selectableChan)
|
|
|
|
// title
|
|
if err := huh.NewInput().
|
|
Title("Issue title:").
|
|
Value(o.Title).
|
|
Validate(huh.ValidateNotEmpty()).
|
|
WithTheme(theme.GetTheme()).
|
|
Run(); err != nil {
|
|
return err
|
|
}
|
|
printTitleAndContent("Issue title:", *o.Title)
|
|
|
|
// description
|
|
if err := huh.NewForm(
|
|
huh.NewGroup(
|
|
huh.NewText().
|
|
Title("Issue description(markdown):").
|
|
ExternalEditor(config.GetPreferences().Editor).
|
|
EditorExtension("md").
|
|
Value(o.Body),
|
|
),
|
|
).
|
|
WithTheme(theme.GetTheme()).
|
|
Run(); err != nil {
|
|
return err
|
|
}
|
|
printTitleAndContent("Issue description(markdown):", *o.Body)
|
|
|
|
// wait until selectables are fetched
|
|
selectables := <-selectableChan
|
|
if selectables.Err != nil {
|
|
return selectables.Err
|
|
}
|
|
|
|
// skip remaining props if we don't have permission to set them
|
|
if !selectables.Repo.Permissions.Push {
|
|
return nil
|
|
}
|
|
|
|
currAssignees := o.SetAssignees
|
|
newAssignees := selectables.Assignees
|
|
|
|
for _, c := range currAssignees {
|
|
if i := slices.Index(newAssignees, c); i != -1 {
|
|
newAssignees = slices.Delete(newAssignees, i, i+1)
|
|
}
|
|
}
|
|
|
|
// assignees
|
|
if currAssignees, err = promptMultiSelectWithPreselect("Set Assignees:", currAssignees, newAssignees, "[other]"); err != nil {
|
|
return err
|
|
}
|
|
if len(currAssignees) == 0 && len(o.SetAssignees) > 0 {
|
|
o.RemoveAssignees = o.SetAssignees
|
|
}
|
|
o.SetAssignees = currAssignees
|
|
printTitleAndContent("Assignees:", strings.Join(o.SetAssignees, "\n"))
|
|
|
|
// milestone
|
|
if len(selectables.MilestoneList) != 0 {
|
|
var defaultMS string
|
|
if o.Milestone != nil {
|
|
defaultMS = *o.Milestone
|
|
}
|
|
if milestoneName, err = promptSelect("Milestone:", selectables.MilestoneList, "", "[none]", defaultMS); err != nil {
|
|
return err
|
|
}
|
|
o.Milestone = &milestoneName
|
|
printTitleAndContent("Milestone:", milestoneName)
|
|
}
|
|
|
|
// labels
|
|
if len(selectables.LabelList) != 0 {
|
|
copy(labelsSelected, o.AddLabels)
|
|
if err := huh.NewMultiSelect[string]().
|
|
Title("Labels:").
|
|
Options(huh.NewOptions(selectables.LabelList...)...).
|
|
Value(&labelsSelected).
|
|
WithTheme(theme.GetTheme()).
|
|
Run(); err != nil {
|
|
return err
|
|
}
|
|
printTitleAndContent("Labels:", strings.Join(labelsSelected, "\n"))
|
|
|
|
// removed labels
|
|
for _, l := range o.AddLabels {
|
|
if !slices.Contains(labelsSelected, l) {
|
|
o.RemoveLabels = append(o.RemoveLabels, l)
|
|
}
|
|
}
|
|
// added labels
|
|
o.AddLabels = make([]string, len(labelsSelected))
|
|
for i, l := range labelsSelected {
|
|
o.AddLabels[i] = l
|
|
}
|
|
}
|
|
|
|
// deadline
|
|
if o.Deadline, err = promptDatetime("Due date:"); err != nil {
|
|
return err
|
|
}
|
|
deadlineStr := "No due date"
|
|
if o.Deadline != nil && !o.Deadline.IsZero() {
|
|
deadlineStr = o.Deadline.Format("2006-01-02")
|
|
}
|
|
printTitleAndContent("Due date:", deadlineStr)
|
|
|
|
return nil
|
|
}
|
|
|
|
func cleanAssignees(list []*gitea.User) []*gitea.User {
|
|
out := make([]*gitea.User, 0, len(list))
|
|
for _, a := range list {
|
|
if strings.TrimSpace(a.UserName) != "" {
|
|
out = append(out, a)
|
|
}
|
|
}
|
|
return out
|
|
}
|