Something went wrong. Try again.
cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 🍃
charm leaflet readability golang
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449package ui
import ( "fmt" "slices" "strings"
"github.com/charmbracelet/lipgloss" "github.com/stormlightlabs/noteleaf/internal/models")
const ( // U+25CF Black Circle StatusTodoSymbol = "●" // U+25D0 Circle with Left Half Black StatusInProgressSymbol = "◐" // U+25A0 Black Square StatusBlockedSymbol = "■" // U+2713 Check Mark StatusDoneSymbol = "✓" // U+26AB Medium Black Circle StatusAbandonedSymbol = "⚫" // U+25CB White Circle StatusPendingSymbol = "○" // U+2713 Check Mark StatusCompletedSymbol = "✓" // U+2717 Ballot X StatusDeletedSymbol = "✗" // U+2605 Black Star PriorityHighSymbol = "★" // U+2606 White Star PriorityMediumSymbol = "☆" // U+25E6 White Bullet PriorityLowSymbol = "◦" // U+25CB White Circle PriorityNoneSymbol = "○" // Three stars PriorityHighPattern = "★★★" // Two stars, one outline PriorityMediumPattern = "★★☆" // One star, two outline PriorityLowPattern = "★☆☆" // Three outline stars PriorityNonePattern = "☆☆☆")
// Type aliases for status and priority styles (now defined in palette.go)var ( TodoStyle = StatusTodo InProgressStyle = StatusInProgress BlockedStyle = StatusBlocked DoneStyle = StatusDone AbandonedStyle = StatusAbandoned PendingStyle = StatusPending CompletedStyle = StatusCompleted DeletedStyle = StatusDeleted PriorityHighStyle = PriorityHigh PriorityMediumStyle = PriorityMedium PriorityLowStyle = PriorityLow PriorityNoneStyle = PriorityNone PriorityLegacyStyle = PriorityLegacy)
// GetStatusSymbol returns the unicode symbol for a given status//// Default to pendingfunc GetStatusSymbol(status string) string { switch status { case models.StatusTodo: return StatusTodoSymbol case models.StatusInProgress: return StatusInProgressSymbol case models.StatusBlocked: return StatusBlockedSymbol case models.StatusDone: return StatusDoneSymbol case models.StatusAbandoned: return StatusAbandonedSymbol case models.StatusPending: return StatusPendingSymbol case models.StatusCompleted: return StatusCompletedSymbol case models.StatusDeleted: return StatusDeletedSymbol default: return StatusPendingSymbol }}
// GetStatusStyle returns the color style for a given status//// Defaults to pending stylefunc GetStatusStyle(status string) lipgloss.Style { switch status { case models.StatusTodo: return TodoStyle case models.StatusInProgress: return InProgressStyle case models.StatusBlocked: return BlockedStyle case models.StatusDone: return DoneStyle case models.StatusAbandoned: return AbandonedStyle case models.StatusPending: return PendingStyle case models.StatusCompleted: return CompletedStyle case models.StatusDeleted: return DeletedStyle default: return PendingStyle }}
// FormatStatusIndicator returns a styled status symbol and textfunc FormatStatusIndicator(status string) string { symbol := GetStatusSymbol(status) style := GetStatusStyle(status) return style.Render(symbol)}
// FormatStatusWithText returns a styled status symbol with status textfunc FormatStatusWithText(status string) string { symbol := GetStatusSymbol(status) style := GetStatusStyle(status) return style.Render(fmt.Sprintf("%s %s", symbol, status))}
// GetStatusDescription returns a human-friendly description of the statusfunc GetStatusDescription(status string) string { switch status { case models.StatusTodo: return "Ready to start" case models.StatusInProgress: return "Currently working" case models.StatusBlocked: return "Waiting on dependency" case models.StatusDone: return "Completed successfully" case models.StatusAbandoned: return "No longer relevant" case models.StatusPending: return "Pending (legacy)" case models.StatusCompleted: return "Completed (legacy)" case models.StatusDeleted: return "Deleted (legacy)" default: return "Unknown status" }}
// FormatTaskStatus returns a complete status display with symbol, status, and descriptionfunc FormatTaskStatus(task *models.Task) string { if task == nil { return "" }
symbol := GetStatusSymbol(task.Status) style := GetStatusStyle(task.Status) description := GetStatusDescription(task.Status)
return fmt.Sprintf("%s %s - %s", style.Render(symbol), style.Render(task.Status), description)}
// StatusLegend returns a formatted legend showing all status symbolsfunc StatusLegend() string { var parts []string
statuses := []string{ models.StatusTodo, models.StatusInProgress, models.StatusBlocked, models.StatusDone, models.StatusAbandoned, }
for _, status := range statuses { parts = append(parts, FormatStatusWithText(status)) }
return strings.Join(parts, " ")}
// GetAllStatusSymbols returns a map of all status symbols for referencefunc GetAllStatusSymbols() map[string]string { return map[string]string{ models.StatusTodo: StatusTodoSymbol, models.StatusInProgress: StatusInProgressSymbol, models.StatusBlocked: StatusBlockedSymbol, models.StatusDone: StatusDoneSymbol, models.StatusAbandoned: StatusAbandonedSymbol, models.StatusPending: StatusPendingSymbol, models.StatusCompleted: StatusCompletedSymbol, models.StatusDeleted: StatusDeletedSymbol, }}
// IsValidStatusTransition checks if a status transition is logically valid//// From todo, can go to in-progress, blocked, done, or abandoned// From in-progress, can go to blocked, done, abandoned, or back to todo// From blocked, can go to todo, in-progress, done, or abandoned// From done, can only be reopened to todo or in-progress// From abandoned, can be reopened to todo or in-progressfunc IsValidStatusTransition(from, to string) bool { if from == models.StatusTodo { validTo := []string{models.StatusInProgress, models.StatusBlocked, models.StatusDone, models.StatusAbandoned} return slices.Contains(validTo, to) }
if from == models.StatusInProgress { validTo := []string{models.StatusTodo, models.StatusBlocked, models.StatusDone, models.StatusAbandoned} return slices.Contains(validTo, to) }
if from == models.StatusBlocked { validTo := []string{models.StatusTodo, models.StatusInProgress, models.StatusDone, models.StatusAbandoned} return slices.Contains(validTo, to) }
if from == models.StatusDone { validTo := []string{models.StatusTodo, models.StatusInProgress} return slices.Contains(validTo, to) }
if from == models.StatusAbandoned { validTo := []string{models.StatusTodo, models.StatusInProgress} return slices.Contains(validTo, to) }
if from == models.StatusPending { return to == models.StatusTodo || to == models.StatusInProgress }
if from == models.StatusCompleted { return to == models.StatusDone }
return false}
// GetPrioritySymbol returns the unicode symbol for a given priorityfunc GetPrioritySymbol(priority string) string { switch priority { case models.PriorityHigh: return PriorityHighSymbol case models.PriorityMedium: return PriorityMediumSymbol case models.PriorityLow: return PriorityLowSymbol case "": return PriorityNoneSymbol default: if len(priority) == 1 && priority >= "A" && priority <= "Z" { return PriorityHighSymbol } switch priority { case "1": return PriorityLowSymbol case "2", "3": return PriorityMediumSymbol case "4", "5": return PriorityHighSymbol default: return PriorityNoneSymbol } }}
// GetPriorityPattern returns the star pattern for a given priorityfunc GetPriorityPattern(priority string) string { switch priority { case models.PriorityHigh: return PriorityHighPattern case models.PriorityMedium: return PriorityMediumPattern case models.PriorityLow: return PriorityLowPattern case "": return PriorityNonePattern default: if len(priority) == 1 && priority >= "A" && priority <= "Z" { if priority <= "C" { return PriorityHighPattern } else if priority <= "M" { return PriorityMediumPattern } else { return PriorityLowPattern } } switch priority { case "1": return PriorityLowPattern case "2", "3": return PriorityMediumPattern case "4", "5": return PriorityHighPattern default: return PriorityNonePattern } }}
// GetPriorityStyle returns the color style for a given priorityfunc GetPriorityStyle(priority string) lipgloss.Style { switch priority { case models.PriorityHigh: return PriorityHighStyle case models.PriorityMedium: return PriorityMediumStyle case models.PriorityLow: return PriorityLowStyle case "": return PriorityNoneStyle default: if len(priority) == 1 && priority >= "A" && priority <= "Z" { return PriorityLegacyStyle } switch priority { case "1", "2", "3", "4", "5": return PriorityLegacyStyle default: return PriorityNoneStyle } }}
// FormatPriorityIndicator returns a styled priority patternfunc FormatPriorityIndicator(priority string) string { pattern := GetPriorityPattern(priority) style := GetPriorityStyle(priority) return style.Render(pattern)}
// FormatPriorityWithText returns a styled priority with text descriptionfunc FormatPriorityWithText(priority string) string { pattern := GetPriorityPattern(priority) style := GetPriorityStyle(priority)
if priority == "" { return style.Render(fmt.Sprintf("%s None", pattern)) }
return style.Render(fmt.Sprintf("%s %s", pattern, priority))}
// GetPriorityDescription returns a human-friendly description of the priorityfunc GetPriorityDescription(priority string) string { switch priority { case models.PriorityHigh: return "Urgent - do first" case models.PriorityMedium: return "Important - schedule soon" case models.PriorityLow: return "Nice to have - when time permits" case "": return "No priority set" default: if len(priority) == 1 && priority >= "A" && priority <= "Z" { return fmt.Sprintf("Priority %s (legacy)", priority) } switch priority { case "1": return "Priority 1 (lowest)" case "2": return "Priority 2 (low)" case "3": return "Priority 3 (medium)" case "4": return "Priority 4 (high)" case "5": return "Priority 5 (highest)" default: return "Unknown priority" } }}
// FormatTaskPriority returns a complete priority display with pattern, priority, and descriptionfunc FormatTaskPriority(task *models.Task) string { if task == nil { return "" }
pattern := GetPriorityPattern(task.Priority) style := GetPriorityStyle(task.Priority) description := GetPriorityDescription(task.Priority)
if task.Priority == "" { return fmt.Sprintf("%s %s", style.Render(pattern), description) }
return fmt.Sprintf("%s %s - %s", style.Render(pattern), style.Render(task.Priority), description)}
// PriorityLegend returns a formatted legend showing all priority patternsfunc PriorityLegend() string { var parts []string
priorities := []string{ models.PriorityHigh, models.PriorityMedium, models.PriorityLow, "", }
for _, priority := range priorities { parts = append(parts, FormatPriorityWithText(priority)) }
return strings.Join(parts, " ")}
// GetAllPrioritySymbols returns a map of all priority symbols for referencefunc GetAllPrioritySymbols() map[string]string { return map[string]string{ models.PriorityHigh: PriorityHighSymbol, models.PriorityMedium: PriorityMediumSymbol, models.PriorityLow: PriorityLowSymbol, "": PriorityNoneSymbol, }}
// GetAllPriorityPatterns returns a map of all priority patterns for referencefunc GetAllPriorityPatterns() map[string]string { return map[string]string{ models.PriorityHigh: PriorityHighPattern, models.PriorityMedium: PriorityMediumPattern, models.PriorityLow: PriorityLowPattern, "": PriorityNonePattern, }}
// GetPriorityDisplayType returns the display type for a priority (text, numeric, or legacy)func GetPriorityDisplayType(priority string) string { switch priority { case models.PriorityHigh, models.PriorityMedium, models.PriorityLow: return "text" case "1", "2", "3", "4", "5": return "numeric" case "": return "none" default: if len(priority) == 1 && priority >= "A" && priority <= "Z" { return "legacy" } return "unknown" }}