Files
gitea-tea/modules/interact/issue_edit.go
T
bircni c5f674111a revert: tea 1.0 rewrite (#1122–#1126) (#1146)
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>
2026-10-18 20:56:33 +00:00

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
}