Files
task-master/internal/ui/views/projects.go
T
skrueckenandCursor 98d2253f44
CI / test-and-build (push) Failing after 1m19s
CI / demo (push) Skipped
Add YouTrack pull/push sync with project settings and time tracking.
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>
2026-10-24 00:17:51 +02:00

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
}