mirror of
https://gitea.com/gitea/tea.git
synced 2025-03-12 04:39:56 +01:00
Feat: interactive issue edit command (#708)
If there are no flags passed to the `issues edit` command, it prompts for changes to the properties like title, description, labels, etc. This is a follow-up to <https://gitea.com/gitea/tea/pulls/506>. Closes: <https://gitea.com/gitea/tea/issues/605> Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com> Reviewed-on: https://gitea.com/gitea/tea/pulls/708 Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com> Co-authored-by: Vincent Neubauer <v.neubauer@darlor.de> Co-committed-by: Vincent Neubauer <v.neubauer@darlor.de>
This commit is contained in:
parent
60636cd7d8
commit
57e3400f0f
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/context"
|
||||
"code.gitea.io/tea/modules/interact"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
@ -47,6 +48,14 @@ func runIssuesEdit(cmd *cli.Context) error {
|
||||
|
||||
client := ctx.Login.Client()
|
||||
for _, opts.Index = range indices {
|
||||
if ctx.NumFlags() == 0 {
|
||||
var err error
|
||||
opts, err = interact.EditIssue(*ctx, opts.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
issue, err := task.EditIssue(ctx, client, *opts)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -70,7 +70,7 @@ func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.Cre
|
||||
|
||||
// milestone
|
||||
if len(selectables.MilestoneList) != 0 {
|
||||
if milestoneName, err = promptSelect("Milestone:", selectables.MilestoneList, "", "[none]"); err != nil {
|
||||
if milestoneName, err = promptSelect("Milestone:", selectables.MilestoneList, "", "[none]", ""); err != nil {
|
||||
return err
|
||||
}
|
||||
o.Milestone = selectables.MilestoneMap[milestoneName]
|
||||
|
153
modules/interact/issue_edit.go
Normal file
153
modules/interact/issue_edit.go
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package interact
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/context"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
)
|
||||
|
||||
// EditIssue interactively edits an issue
|
||||
func EditIssue(ctx context.TeaContext, index int64) (*task.EditIssueOption, error) {
|
||||
var opts = task.EditIssueOption{}
|
||||
var err error
|
||||
|
||||
ctx.Owner, ctx.Repo, err = promptRepoSlug(ctx.Owner, ctx.Repo)
|
||||
if err != nil {
|
||||
return &opts, err
|
||||
}
|
||||
|
||||
c := ctx.Login.Client()
|
||||
i, _, err := c.GetIssue(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,
|
||||
}
|
||||
|
||||
if len(i.Assignees) != 0 {
|
||||
for _, a := range i.Assignees {
|
||||
opts.AddAssignees = append(opts.AddAssignees, 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(&ctx, &opts); err != nil {
|
||||
return &opts, err
|
||||
}
|
||||
|
||||
return &opts, err
|
||||
}
|
||||
|
||||
func promptIssueEditProperties(ctx *context.TeaContext, o *task.EditIssueOption) error {
|
||||
var milestoneName string
|
||||
var labelsSelected []string
|
||||
var err error
|
||||
|
||||
selectableChan := make(chan (issueSelectables), 1)
|
||||
go fetchIssueSelectables(ctx.Login, ctx.Owner, ctx.Repo, selectableChan)
|
||||
|
||||
// title
|
||||
promptOpts := survey.WithValidator(survey.Required)
|
||||
promptI := &survey.Input{Message: "Issue title:", Default: *o.Title}
|
||||
if err = survey.AskOne(promptI, o.Title, promptOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// description
|
||||
promptD := NewMultiline(Multiline{
|
||||
Message: "Issue description:",
|
||||
Default: *o.Body,
|
||||
Syntax: "md",
|
||||
UseEditor: config.GetPreferences().Editor,
|
||||
EditorAppendDefault: true,
|
||||
EditorHideDefault: true,
|
||||
})
|
||||
|
||||
if err = survey.AskOne(promptD, o.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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.AddAssignees
|
||||
newAssignees := selectables.Assignees
|
||||
|
||||
for _, c := range currAssignees {
|
||||
if i := slices.Index(newAssignees, c); i != -1 {
|
||||
newAssignees = slices.Delete(newAssignees, i, i+1)
|
||||
}
|
||||
}
|
||||
|
||||
// assignees
|
||||
if o.AddAssignees, err = promptMultiSelect("Add Assignees:", newAssignees, "[other]"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// labels
|
||||
if len(selectables.LabelList) != 0 {
|
||||
promptL := &survey.MultiSelect{Message: "Labels:", Options: selectables.LabelList, VimMode: true, Default: o.AddLabels}
|
||||
if err := survey.AskOne(promptL, &labelsSelected); err != nil {
|
||||
return err
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -96,7 +96,7 @@ func CreateLogin() error {
|
||||
}
|
||||
|
||||
if sshKey == "" {
|
||||
sshKey, err = promptSelect("Select ssh-key: ", task.ListSSHPubkey(), "", "")
|
||||
sshKey, err = promptSelect("Select ssh-key: ", task.ListSSHPubkey(), "", "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ type Multiline struct {
|
||||
Default string
|
||||
Syntax string
|
||||
UseEditor bool
|
||||
EditorAppendDefault bool
|
||||
EditorHideDefault bool
|
||||
}
|
||||
|
||||
// NewMultiline creates a prompt that switches between the inline multiline text
|
||||
@ -29,6 +31,8 @@ func NewMultiline(opts Multiline) (prompt survey.Prompt) {
|
||||
Message: opts.Message,
|
||||
Default: opts.Default,
|
||||
FileName: "*." + opts.Syntax,
|
||||
AppendDefault: opts.EditorAppendDefault,
|
||||
HideDefault: opts.EditorHideDefault,
|
||||
}
|
||||
} else {
|
||||
prompt = &survey.Multiline{Message: opts.Message, Default: opts.Default}
|
||||
@ -146,13 +150,17 @@ func promptSelectV2(prompt string, options []string) (string, error) {
|
||||
}
|
||||
|
||||
// promptSelect creates a generic select prompt, with processing of custom values or none-option.
|
||||
func promptSelect(prompt string, options []string, customVal, noneVal string) (string, error) {
|
||||
func promptSelect(prompt string, options []string, customVal, noneVal, defaultVal string) (string, error) {
|
||||
var selection string
|
||||
if defaultVal == "" && noneVal != "" {
|
||||
defaultVal = noneVal
|
||||
|
||||
}
|
||||
promptA := &survey.Select{
|
||||
Message: prompt,
|
||||
Options: makeSelectOpts(options, customVal, noneVal),
|
||||
VimMode: true,
|
||||
Default: noneVal,
|
||||
Default: defaultVal,
|
||||
}
|
||||
if err := survey.AskOne(promptA, &selection); err != nil {
|
||||
return "", err
|
||||
|
Loading…
x
Reference in New Issue
Block a user