Wire manual Y menu and CLI sync for issues and work items, persist per-project credentials, and keep local edits until an explicit push. Co-authored-by: Cursor <cursoragent@cursor.com>
105 lines
2.6 KiB
Go
105 lines
2.6 KiB
Go
package views
|
|
|
|
import (
|
|
"unicode"
|
|
|
|
"github.com/charmbracelet/bubbles/list"
|
|
|
|
"task/internal/project"
|
|
)
|
|
|
|
// ProjectItem wraps a project for bubbles/list.
|
|
type ProjectItem struct {
|
|
Project project.Project
|
|
Active bool
|
|
YouTrack bool // true when YouTrack sync is configured
|
|
}
|
|
|
|
func (i ProjectItem) Title() string {
|
|
name := i.Project.Name
|
|
if i.YouTrack {
|
|
name += " [YT]"
|
|
}
|
|
if i.Active {
|
|
return name + " *"
|
|
}
|
|
return name
|
|
}
|
|
func (i ProjectItem) Description() string { return i.Project.ID }
|
|
func (i ProjectItem) FilterValue() string { return i.Project.Name }
|
|
|
|
// NewProjectList builds a list of projects.
|
|
func NewProjectList(projects []project.Project, activeID string, width, height int) list.Model {
|
|
return NewProjectListWithYT(projects, activeID, nil, width, height)
|
|
}
|
|
|
|
// NewProjectListWithYT builds a list, marking projects with YouTrack config.
|
|
func NewProjectListWithYT(projects []project.Project, activeID string, ytConfigured map[string]bool, width, height int) list.Model {
|
|
items := make([]list.Item, len(projects))
|
|
selected := 0
|
|
for i, p := range projects {
|
|
items[i] = ProjectItem{
|
|
Project: p,
|
|
Active: p.ID == activeID,
|
|
YouTrack: ytConfigured[p.ID],
|
|
}
|
|
if p.ID == activeID {
|
|
selected = i
|
|
}
|
|
}
|
|
|
|
delegate := list.NewDefaultDelegate()
|
|
delegate.ShowDescription = false
|
|
l := list.New(items, delegate, width, height)
|
|
l.Title = "Projects"
|
|
l.SetShowStatusBar(false)
|
|
l.SetShowHelp(false)
|
|
l.SetFilteringEnabled(false)
|
|
l.DisableQuitKeybindings()
|
|
if len(items) > 0 {
|
|
l.Select(selected)
|
|
}
|
|
return l
|
|
}
|
|
|
|
// JumpProjectByLetter cycles to the next project whose name starts with r.
|
|
func JumpProjectByLetter(l *list.Model, r rune) bool {
|
|
r = unicode.ToLower(r)
|
|
items := l.Items()
|
|
n := len(items)
|
|
if n == 0 {
|
|
return false
|
|
}
|
|
start := l.Index()
|
|
for step := 1; step <= n; step++ {
|
|
i := (start + step) % n
|
|
pi, ok := items[i].(ProjectItem)
|
|
if !ok {
|
|
continue
|
|
}
|
|
name := []rune(pi.Project.Name)
|
|
if len(name) == 0 {
|
|
continue
|
|
}
|
|
if unicode.ToLower(name[0]) == r {
|
|
l.Select(i)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ProjectsToItems converts projects to list items.
|
|
func ProjectsToItems(projects []project.Project, activeID string) []list.Item {
|
|
return ProjectsToItemsWithYT(projects, activeID, nil)
|
|
}
|
|
|
|
// ProjectsToItemsWithYT converts projects to list items with YouTrack flags.
|
|
func ProjectsToItemsWithYT(projects []project.Project, activeID string, ytConfigured map[string]bool) []list.Item {
|
|
items := make([]list.Item, len(projects))
|
|
for i, p := range projects {
|
|
items[i] = ProjectItem{Project: p, Active: p.ID == activeID, YouTrack: ytConfigured[p.ID]}
|
|
}
|
|
return items
|
|
}
|