// Copyright 2025 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package interact import ( "slices" "strings" "code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/theme" "github.com/charmbracelet/huh" ) // EditIssue interactively edits an issue func EditIssue(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.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 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.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 } printTitleAndContent("Assignees:", strings.Join(o.AddAssignees, "\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 }