Handle UDAs for editing; Fix layout; Add annotations
This commit is contained in:
@ -15,7 +15,7 @@ type Common struct {
|
||||
TW taskwarrior.TaskWarrior
|
||||
Keymap *Keymap
|
||||
Styles *Styles
|
||||
Udas []string
|
||||
Udas []taskwarrior.Uda
|
||||
|
||||
pageStack *Stack[Component]
|
||||
width int
|
||||
@ -54,5 +54,11 @@ func (c *Common) PushPage(page Component) {
|
||||
}
|
||||
|
||||
func (c *Common) PopPage() (Component, error) {
|
||||
return c.pageStack.Pop()
|
||||
component, err := c.pageStack.Pop()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
component.SetSize(c.width, c.height)
|
||||
return component, nil
|
||||
}
|
||||
|
||||
200
common/styles.go
200
common/styles.go
@ -20,6 +20,8 @@ type TableStyle struct {
|
||||
}
|
||||
|
||||
type Styles struct {
|
||||
Colors map[string]*lipgloss.Style
|
||||
|
||||
Base lipgloss.Style
|
||||
|
||||
Form *huh.Theme
|
||||
@ -28,61 +30,22 @@ type Styles struct {
|
||||
ColumnFocused lipgloss.Style
|
||||
ColumnBlurred lipgloss.Style
|
||||
ColumnInsert lipgloss.Style
|
||||
|
||||
Colors map[string]lipgloss.Style
|
||||
|
||||
// TODO: make color config completely dynamic to account for keyword., project., tag. and uda. colors
|
||||
Active lipgloss.Style
|
||||
Alternate lipgloss.Style
|
||||
Blocked lipgloss.Style
|
||||
Blocking lipgloss.Style
|
||||
BurndownDone lipgloss.Style
|
||||
BurndownPending lipgloss.Style
|
||||
BurndownStarted lipgloss.Style
|
||||
CalendarDue lipgloss.Style
|
||||
CalendarDueToday lipgloss.Style
|
||||
CalendarHoliday lipgloss.Style
|
||||
CalendarOverdue lipgloss.Style
|
||||
CalendarScheduled lipgloss.Style
|
||||
CalendarToday lipgloss.Style
|
||||
CalendarWeekend lipgloss.Style
|
||||
CalendarWeeknumber lipgloss.Style
|
||||
Completed lipgloss.Style
|
||||
Debug lipgloss.Style
|
||||
Deleted lipgloss.Style
|
||||
Due lipgloss.Style
|
||||
DueToday lipgloss.Style
|
||||
Error lipgloss.Style
|
||||
Footnote lipgloss.Style
|
||||
Header lipgloss.Style
|
||||
HistoryAdd lipgloss.Style
|
||||
HistoryDelete lipgloss.Style
|
||||
HistoryDone lipgloss.Style
|
||||
Label lipgloss.Style
|
||||
LabelSort lipgloss.Style
|
||||
Overdue lipgloss.Style
|
||||
ProjectNone lipgloss.Style
|
||||
Recurring lipgloss.Style
|
||||
Scheduled lipgloss.Style
|
||||
SummaryBackground lipgloss.Style
|
||||
SummaryBar lipgloss.Style
|
||||
SyncAdded lipgloss.Style
|
||||
SyncChanged lipgloss.Style
|
||||
SyncRejected lipgloss.Style
|
||||
TagNext lipgloss.Style
|
||||
TagNone lipgloss.Style
|
||||
Tagged lipgloss.Style
|
||||
UdaPriorityH lipgloss.Style
|
||||
UdaPriorityL lipgloss.Style
|
||||
UdaPriorityM lipgloss.Style
|
||||
UndoAfter lipgloss.Style
|
||||
UndoBefore lipgloss.Style
|
||||
Until lipgloss.Style
|
||||
Warning lipgloss.Style
|
||||
}
|
||||
|
||||
func NewStyles(config *taskwarrior.TWConfig) *Styles {
|
||||
styles := parseColors(config.GetConfig())
|
||||
styles := Styles{}
|
||||
|
||||
colors := make(map[string]*lipgloss.Style)
|
||||
|
||||
for key, value := range config.GetConfig() {
|
||||
if strings.HasPrefix(key, "color.") {
|
||||
_, color, _ := strings.Cut(key, ".")
|
||||
colors[color] = parseColorString(value)
|
||||
}
|
||||
}
|
||||
|
||||
styles.Colors = colors
|
||||
|
||||
styles.Base = lipgloss.NewStyle()
|
||||
|
||||
styles.TableStyle = TableStyle{
|
||||
@ -94,11 +57,12 @@ func NewStyles(config *taskwarrior.TWConfig) *Styles {
|
||||
|
||||
formTheme := huh.ThemeBase()
|
||||
formTheme.Focused.Title = formTheme.Focused.Title.Bold(true)
|
||||
formTheme.Focused.SelectSelector = formTheme.Focused.SelectSelector.SetString("> ")
|
||||
formTheme.Focused.SelectSelector = formTheme.Focused.SelectSelector.SetString("→ ")
|
||||
formTheme.Focused.SelectedOption = formTheme.Focused.SelectedOption.Bold(true)
|
||||
formTheme.Focused.MultiSelectSelector = formTheme.Focused.MultiSelectSelector.SetString("> ")
|
||||
formTheme.Focused.MultiSelectSelector = formTheme.Focused.MultiSelectSelector.SetString("→ ")
|
||||
formTheme.Focused.SelectedPrefix = formTheme.Focused.SelectedPrefix.SetString("✓ ")
|
||||
formTheme.Focused.UnselectedPrefix = formTheme.Focused.SelectedPrefix.SetString("• ")
|
||||
formTheme.Blurred.Title = formTheme.Blurred.Title.Bold(true)
|
||||
formTheme.Blurred.SelectSelector = formTheme.Blurred.SelectSelector.SetString(" ")
|
||||
formTheme.Blurred.SelectedOption = formTheme.Blurred.SelectedOption.Bold(true)
|
||||
formTheme.Blurred.MultiSelectSelector = formTheme.Blurred.MultiSelectSelector.SetString(" ")
|
||||
@ -107,131 +71,23 @@ func NewStyles(config *taskwarrior.TWConfig) *Styles {
|
||||
|
||||
styles.Form = formTheme
|
||||
|
||||
styles.ColumnFocused = lipgloss.NewStyle().Width(50).Height(30).Border(lipgloss.DoubleBorder(), true)
|
||||
styles.ColumnBlurred = lipgloss.NewStyle().Width(50).Height(30).Border(lipgloss.HiddenBorder(), true)
|
||||
styles.ColumnInsert = lipgloss.NewStyle().Width(50).Height(30).Border(lipgloss.DoubleBorder(), true).BorderForeground(styles.Active.GetForeground())
|
||||
|
||||
return styles
|
||||
}
|
||||
|
||||
func parseColors(config map[string]string) *Styles {
|
||||
styles := Styles{}
|
||||
colors := make(map[string]lipgloss.Style)
|
||||
|
||||
for key, value := range config {
|
||||
if strings.HasPrefix(key, "color.") {
|
||||
_, colorValue, _ := strings.Cut(key, ".")
|
||||
colors[colorValue] = parseColorString(value)
|
||||
switch colorValue {
|
||||
case "active":
|
||||
styles.Active = parseColorString(value)
|
||||
case "alternate":
|
||||
styles.Alternate = parseColorString(value)
|
||||
case "blocked":
|
||||
styles.Blocked = parseColorString(value)
|
||||
case "blocking":
|
||||
styles.Blocking = parseColorString(value)
|
||||
case "burndown.done":
|
||||
styles.BurndownDone = parseColorString(value)
|
||||
case "burndown.pending":
|
||||
styles.BurndownPending = parseColorString(value)
|
||||
case "burndown.started":
|
||||
styles.BurndownStarted = parseColorString(value)
|
||||
case "calendar.due":
|
||||
styles.CalendarDue = parseColorString(value)
|
||||
case "calendar.due.today":
|
||||
styles.CalendarDueToday = parseColorString(value)
|
||||
case "calendar.holiday":
|
||||
styles.CalendarHoliday = parseColorString(value)
|
||||
case "calendar.overdue":
|
||||
styles.CalendarOverdue = parseColorString(value)
|
||||
case "calendar.scheduled":
|
||||
styles.CalendarScheduled = parseColorString(value)
|
||||
case "calendar.today":
|
||||
styles.CalendarToday = parseColorString(value)
|
||||
case "calendar.weekend":
|
||||
styles.CalendarWeekend = parseColorString(value)
|
||||
case "calendar.weeknumber":
|
||||
styles.CalendarWeeknumber = parseColorString(value)
|
||||
case "completed":
|
||||
styles.Completed = parseColorString(value)
|
||||
case "debug":
|
||||
styles.Debug = parseColorString(value)
|
||||
case "deleted":
|
||||
styles.Deleted = parseColorString(value)
|
||||
case "due":
|
||||
styles.Due = parseColorString(value)
|
||||
case "due.today":
|
||||
styles.DueToday = parseColorString(value)
|
||||
case "error":
|
||||
styles.Error = parseColorString(value)
|
||||
case "footnote":
|
||||
styles.Footnote = parseColorString(value)
|
||||
case "header":
|
||||
styles.Header = parseColorString(value)
|
||||
case "history.add":
|
||||
styles.HistoryAdd = parseColorString(value)
|
||||
case "history.delete":
|
||||
styles.HistoryDelete = parseColorString(value)
|
||||
case "history.done":
|
||||
styles.HistoryDone = parseColorString(value)
|
||||
case "label":
|
||||
styles.Label = parseColorString(value)
|
||||
case "label.sort":
|
||||
styles.LabelSort = parseColorString(value)
|
||||
case "overdue":
|
||||
styles.Overdue = parseColorString(value)
|
||||
case "project.none":
|
||||
styles.ProjectNone = parseColorString(value)
|
||||
case "recurring":
|
||||
styles.Recurring = parseColorString(value)
|
||||
case "scheduled":
|
||||
styles.Scheduled = parseColorString(value)
|
||||
case "summary.background":
|
||||
styles.SummaryBackground = parseColorString(value)
|
||||
case "summary.bar":
|
||||
styles.SummaryBar = parseColorString(value)
|
||||
case "sync.added":
|
||||
styles.SyncAdded = parseColorString(value)
|
||||
case "sync.changed":
|
||||
styles.SyncChanged = parseColorString(value)
|
||||
case "sync.rejected":
|
||||
styles.SyncRejected = parseColorString(value)
|
||||
case "tag.next":
|
||||
styles.TagNext = parseColorString(value)
|
||||
case "tag.none":
|
||||
styles.TagNone = parseColorString(value)
|
||||
case "tagged":
|
||||
styles.Tagged = parseColorString(value)
|
||||
case "uda.priority.H":
|
||||
styles.UdaPriorityH = parseColorString(value)
|
||||
case "uda.priority.L":
|
||||
styles.UdaPriorityL = parseColorString(value)
|
||||
case "uda.priority.M":
|
||||
styles.UdaPriorityM = parseColorString(value)
|
||||
case "undo.after":
|
||||
styles.UndoAfter = parseColorString(value)
|
||||
case "undo.before":
|
||||
styles.UndoBefore = parseColorString(value)
|
||||
case "until":
|
||||
styles.Until = parseColorString(value)
|
||||
case "warning":
|
||||
styles.Warning = parseColorString(value)
|
||||
}
|
||||
}
|
||||
styles.ColumnFocused = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1)
|
||||
styles.ColumnBlurred = lipgloss.NewStyle().Border(lipgloss.HiddenBorder(), true).Padding(1)
|
||||
styles.ColumnInsert = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1)
|
||||
if styles.Colors["active"] != nil {
|
||||
styles.ColumnInsert = styles.ColumnInsert.BorderForeground(styles.Colors["active"].GetForeground())
|
||||
}
|
||||
|
||||
styles.Colors = colors
|
||||
|
||||
return &styles
|
||||
}
|
||||
|
||||
func parseColorString(color string) lipgloss.Style {
|
||||
style := lipgloss.NewStyle()
|
||||
func parseColorString(color string) *lipgloss.Style {
|
||||
if color == "" {
|
||||
return style
|
||||
return nil
|
||||
}
|
||||
|
||||
style := lipgloss.NewStyle()
|
||||
|
||||
if strings.Contains(color, "on") {
|
||||
fgbg := strings.Split(color, "on")
|
||||
fg := strings.TrimSpace(fgbg[0])
|
||||
@ -246,7 +102,7 @@ func parseColorString(color string) lipgloss.Style {
|
||||
style = style.Foreground(parseColor(strings.TrimSpace(color)))
|
||||
}
|
||||
|
||||
return style
|
||||
return &style
|
||||
}
|
||||
|
||||
func parseColor(color string) lipgloss.Color {
|
||||
|
||||
@ -149,65 +149,85 @@ func (m *Model) parseRowStyles(rows taskwarrior.Tasks) []lipgloss.Style {
|
||||
if len(rows) == 0 {
|
||||
return styles
|
||||
}
|
||||
taskstyle:
|
||||
for i, task := range rows {
|
||||
if task.Status == "deleted" {
|
||||
styles[i] = m.common.Styles.Deleted.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
if c, ok := m.common.Styles.Colors["deleted"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
}
|
||||
if task.Status == "completed" {
|
||||
styles[i] = m.common.Styles.Completed.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
if c, ok := m.common.Styles.Colors["completed"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
}
|
||||
if task.Status == "pending" && task.Start != "" {
|
||||
styles[i] = m.common.Styles.Active.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
if c, ok := m.common.Styles.Colors["active"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
}
|
||||
// TODO: implement keyword
|
||||
// TODO: implement tag
|
||||
if task.HasTag("next") {
|
||||
if c, ok := m.common.Styles.Colors["tag.next"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
}
|
||||
// TODO: implement project
|
||||
if !task.GetDate("due").IsZero() && task.GetDate("due").Before(time.Now()) {
|
||||
styles[i] = m.common.Styles.Overdue.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
if c, ok := m.common.Styles.Colors["overdue"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
}
|
||||
if task.Scheduled != "" {
|
||||
styles[i] = m.common.Styles.Scheduled.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
if c, ok := m.common.Styles.Colors["scheduled"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !task.GetDate("due").IsZero() && task.GetDate("due").Truncate(24*time.Hour).Equal(time.Now().Truncate(24*time.Hour)) {
|
||||
styles[i] = m.common.Styles.DueToday.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
if c, ok := m.common.Styles.Colors["due.today"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
}
|
||||
if task.Due != "" {
|
||||
styles[i] = m.common.Styles.Due.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
if c, ok := m.common.Styles.Colors["due"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(task.Depends) > 0 {
|
||||
styles[i] = m.common.Styles.Blocked.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
if c, ok := m.common.Styles.Colors["blocked"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
}
|
||||
// TODO implement blocking
|
||||
if task.Recur != "" {
|
||||
styles[i] = m.common.Styles.Recurring.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
if c, ok := m.common.Styles.Colors["recurring"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
}
|
||||
// TODO: make styles optional and discard if empty
|
||||
if len(task.Tags) > 0 {
|
||||
styles[i] = m.common.Styles.Tagged.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
taskIteration:
|
||||
for _, tag := range task.Tags {
|
||||
if tag == "next" {
|
||||
styles[i] = m.common.Styles.TagNext.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
break taskIteration
|
||||
}
|
||||
if c, ok := m.common.Styles.Colors["tagged"]; ok && c != nil {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(m.common.Udas) > 0 {
|
||||
for _, uda := range m.common.Udas {
|
||||
if u, ok := task.Udas[uda]; ok {
|
||||
if style, ok := m.common.Styles.Colors[fmt.Sprintf("uda.%s.%s", uda, u)]; ok {
|
||||
styles[i] = style.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue
|
||||
if u, ok := task.Udas[uda.Name]; ok {
|
||||
if c, ok := m.common.Styles.Colors[fmt.Sprintf("uda.%s.%s", uda.Name, u)]; ok {
|
||||
styles[i] = c.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding())
|
||||
continue taskstyle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
go.mod
10
go.mod
@ -5,6 +5,7 @@ go 1.22.2
|
||||
require (
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.26.1
|
||||
github.com/charmbracelet/glamour v0.7.0
|
||||
github.com/charmbracelet/huh v0.3.0
|
||||
github.com/charmbracelet/lipgloss v0.10.1-0.20240506202754-3ee5dcab73cb
|
||||
github.com/mattn/go-runewidth v0.0.15
|
||||
@ -12,20 +13,29 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma/v2 v2.8.0 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/catppuccin/go v0.2.0 // indirect
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240506152644-8135bef4e495 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.25 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
|
||||
github.com/yuin/goldmark v1.5.4 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.2 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
|
||||
28
go.sum
28
go.sum
@ -1,21 +1,37 @@
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||
github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264=
|
||||
github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
|
||||
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.26.1 h1:xujcQeF73rh4jwu3+zhfQsvV18x+7zIjlw7/CYbzGJ0=
|
||||
github.com/charmbracelet/bubbletea v0.26.1/go.mod h1:FzKr7sKoO8iFVcdIBM9J0sJOcQv5nDQaYwsee3kpbgo=
|
||||
github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng=
|
||||
github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps=
|
||||
github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE=
|
||||
github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA=
|
||||
github.com/charmbracelet/lipgloss v0.10.1-0.20240506202754-3ee5dcab73cb h1:Hs3xzxHuruNT2Iuo87iS40c0PhLqpnUKBI6Xw6Ad3wQ=
|
||||
github.com/charmbracelet/lipgloss v0.10.1-0.20240506202754-3ee5dcab73cb/go.mod h1:EPP2QJ0ectp3zo6gx9f8oJGq8keirqPJ3XpYEI8wrrs=
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240506152644-8135bef4e495 h1:+0U9qX8Pv8KiYgRxfBvORRjgBzLgHMjtElP4O0PyKYA=
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240506152644-8135bef4e495/go.mod h1:qeR6w1zITbkF7vEhcx0CqX5GfnIiQloJWQghN6HfP+c=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
@ -24,9 +40,12 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
@ -35,12 +54,21 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
|
||||
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s=
|
||||
github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
@ -52,9 +52,9 @@ func prevArea() tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
type changeAreaMsg area
|
||||
type changeAreaMsg int
|
||||
|
||||
func changeArea(a area) tea.Cmd {
|
||||
func changeArea(a int) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return changeAreaMsg(a)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"tasksquire/taskwarrior"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
|
||||
// "github.com/charmbracelet/bubbles/table"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
@ -24,7 +25,7 @@ type ReportPage struct {
|
||||
|
||||
taskTable table.Model
|
||||
|
||||
subpage tea.Model
|
||||
subpage common.Component
|
||||
}
|
||||
|
||||
func NewReportPage(com *common.Common, report *taskwarrior.Report) *ReportPage {
|
||||
@ -54,8 +55,8 @@ func NewReportPage(com *common.Common, report *taskwarrior.Report) *ReportPage {
|
||||
func (p *ReportPage) SetSize(width int, height int) {
|
||||
p.common.SetSize(width, height)
|
||||
|
||||
p.taskTable.SetWidth(width - p.common.Styles.Base.GetVerticalFrameSize())
|
||||
p.taskTable.SetHeight(height - p.common.Styles.Base.GetHorizontalFrameSize())
|
||||
p.taskTable.SetWidth(width - p.common.Styles.Base.GetHorizontalFrameSize())
|
||||
p.taskTable.SetHeight(height - p.common.Styles.Base.GetVerticalFrameSize())
|
||||
}
|
||||
|
||||
func (p *ReportPage) Init() tea.Cmd {
|
||||
|
||||
@ -5,12 +5,15 @@ import (
|
||||
"log/slog"
|
||||
"strings"
|
||||
"tasksquire/common"
|
||||
"time"
|
||||
|
||||
"tasksquire/taskwarrior"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/glamour"
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
@ -26,13 +29,16 @@ type TaskEditorPage struct {
|
||||
common *common.Common
|
||||
task taskwarrior.Task
|
||||
|
||||
colWidth int
|
||||
colHeight int
|
||||
|
||||
mode mode
|
||||
|
||||
columnCursor int
|
||||
|
||||
area area
|
||||
area int
|
||||
areaPicker *areaPicker
|
||||
areas map[area]tea.Model
|
||||
areas []area
|
||||
}
|
||||
|
||||
func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPage {
|
||||
@ -41,21 +47,17 @@ func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPag
|
||||
task: task,
|
||||
}
|
||||
|
||||
if p.task.Priority == "" {
|
||||
p.task.Priority = "(none)"
|
||||
}
|
||||
if p.task.Project == "" {
|
||||
p.task.Project = "(none)"
|
||||
}
|
||||
|
||||
priorityOptions := append([]string{"(none)"}, p.common.TW.GetPriorities()...)
|
||||
projectOptions := append([]string{"(none)"}, p.common.TW.GetProjects()...)
|
||||
tagOptions := p.common.TW.GetTags()
|
||||
|
||||
p.areas = map[area]tea.Model{
|
||||
areaTask: NewTaskEdit(p.common, &p.task.Description, &p.task.Priority, &p.task.Project, priorityOptions, projectOptions),
|
||||
areaTags: NewTagEdit(p.common, &p.task.Tags, tagOptions),
|
||||
areaTime: NewTimeEdit(p.common, &p.task.Due, &p.task.Scheduled, &p.task.Wait, &p.task.Until),
|
||||
p.areas = []area{
|
||||
NewTaskEdit(p.common, &p.task),
|
||||
NewTagEdit(p.common, &p.task.Tags, tagOptions),
|
||||
NewTimeEdit(p.common, &p.task.Due, &p.task.Scheduled, &p.task.Wait, &p.task.Until),
|
||||
NewDetailsEdit(p.common, &p.task),
|
||||
}
|
||||
|
||||
// p.areaList = NewAreaList(common, areaItems)
|
||||
@ -66,17 +68,30 @@ func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPag
|
||||
|
||||
p.columnCursor = 1
|
||||
if p.task.Uuid == "" {
|
||||
// p.mode = modeInsert
|
||||
p.mode = modeInsert
|
||||
} else {
|
||||
p.mode = modeNormal
|
||||
}
|
||||
|
||||
p.SetSize(com.Width(), com.Height())
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *TaskEditorPage) SetSize(width, height int) {
|
||||
p.common.SetSize(width, height)
|
||||
|
||||
if width >= 70 {
|
||||
p.colWidth = 70 - p.common.Styles.ColumnFocused.GetVerticalFrameSize()
|
||||
} else {
|
||||
p.colWidth = width - p.common.Styles.ColumnFocused.GetVerticalFrameSize()
|
||||
}
|
||||
|
||||
if height >= 40 {
|
||||
p.colHeight = 40 - p.common.Styles.ColumnFocused.GetVerticalFrameSize()
|
||||
} else {
|
||||
p.colHeight = height - p.common.Styles.ColumnFocused.GetVerticalFrameSize()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *TaskEditorPage) Init() tea.Cmd {
|
||||
@ -85,8 +100,10 @@ func (p *TaskEditorPage) Init() tea.Cmd {
|
||||
|
||||
func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
p.SetSize(msg.Width, msg.Height)
|
||||
case changeAreaMsg:
|
||||
p.area = area(msg)
|
||||
p.area = int(msg)
|
||||
case changeModeMsg:
|
||||
p.mode = mode(msg)
|
||||
case prevColumnMsg:
|
||||
@ -102,13 +119,15 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case prevAreaMsg:
|
||||
p.area--
|
||||
if p.area < 0 {
|
||||
p.area = 2
|
||||
p.area = len(p.areas) - 1
|
||||
}
|
||||
p.areas[p.area].SetCursor(-1)
|
||||
case nextAreaMsg:
|
||||
p.area++
|
||||
if p.area > 2 {
|
||||
if p.area > len(p.areas)-1 {
|
||||
p.area = 0
|
||||
}
|
||||
p.areas[p.area].SetCursor(0)
|
||||
}
|
||||
|
||||
switch p.mode {
|
||||
@ -137,23 +156,23 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case key.Matches(msg, p.common.Keymap.Right):
|
||||
return p, nextColumn()
|
||||
case key.Matches(msg, p.common.Keymap.Up):
|
||||
var cmd tea.Cmd
|
||||
if p.columnCursor == 0 {
|
||||
picker, cmd := p.areaPicker.Update(msg)
|
||||
p.areaPicker = picker.(*areaPicker)
|
||||
return p, cmd
|
||||
} else {
|
||||
p.areas[p.area], cmd = p.areas[p.area].Update(prevFieldMsg{})
|
||||
model, cmd := p.areas[p.area].Update(prevFieldMsg{})
|
||||
p.areas[p.area] = model.(area)
|
||||
return p, cmd
|
||||
}
|
||||
case key.Matches(msg, p.common.Keymap.Down):
|
||||
var cmd tea.Cmd
|
||||
if p.columnCursor == 0 {
|
||||
picker, cmd := p.areaPicker.Update(msg)
|
||||
p.areaPicker = picker.(*areaPicker)
|
||||
return p, cmd
|
||||
} else {
|
||||
p.areas[p.area], cmd = p.areas[p.area].Update(nextFieldMsg{})
|
||||
model, cmd := p.areas[p.area].Update(nextFieldMsg{})
|
||||
p.areas[p.area] = model.(area)
|
||||
return p, cmd
|
||||
}
|
||||
}
|
||||
@ -175,39 +194,39 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case key.Matches(msg, p.common.Keymap.Back):
|
||||
return p, changeMode(modeNormal)
|
||||
case key.Matches(msg, p.common.Keymap.Prev):
|
||||
var cmd tea.Cmd
|
||||
if p.columnCursor == 0 {
|
||||
picker, cmd := p.areaPicker.Update(msg)
|
||||
p.areaPicker = picker.(*areaPicker)
|
||||
return p, cmd
|
||||
} else {
|
||||
p.areas[p.area], cmd = p.areas[p.area].Update(prevFieldMsg{})
|
||||
model, cmd := p.areas[p.area].Update(prevFieldMsg{})
|
||||
p.areas[p.area] = model.(area)
|
||||
return p, cmd
|
||||
}
|
||||
case key.Matches(msg, p.common.Keymap.Next):
|
||||
var cmd tea.Cmd
|
||||
if p.columnCursor == 0 {
|
||||
picker, cmd := p.areaPicker.Update(msg)
|
||||
p.areaPicker = picker.(*areaPicker)
|
||||
return p, cmd
|
||||
} else {
|
||||
p.areas[p.area], cmd = p.areas[p.area].Update(nextFieldMsg{})
|
||||
model, cmd := p.areas[p.area].Update(nextFieldMsg{})
|
||||
p.areas[p.area] = model.(area)
|
||||
return p, cmd
|
||||
}
|
||||
case key.Matches(msg, p.common.Keymap.Ok):
|
||||
area, cmd := p.areas[p.area].Update(msg)
|
||||
p.areas[p.area] = area
|
||||
model, cmd := p.areas[p.area].Update(msg)
|
||||
p.areas[p.area] = model.(area)
|
||||
return p, tea.Batch(cmd, nextField())
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
if p.columnCursor == 0 {
|
||||
picker, cmd := p.areaPicker.Update(msg)
|
||||
p.areaPicker = picker.(*areaPicker)
|
||||
return p, cmd
|
||||
} else {
|
||||
p.areas[p.area], cmd = p.areas[p.area].Update(msg)
|
||||
model, cmd := p.areas[p.area].Update(msg)
|
||||
p.areas[p.area] = model.(area)
|
||||
return p, cmd
|
||||
}
|
||||
}
|
||||
@ -215,143 +234,45 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (p *TaskEditorPage) View() string {
|
||||
var focusStyle lipgloss.Style
|
||||
var focusedStyle, blurredStyle lipgloss.Style
|
||||
if p.mode == modeInsert {
|
||||
focusStyle = p.common.Styles.ColumnInsert
|
||||
focusedStyle = p.common.Styles.ColumnInsert.Width(p.colWidth).Height(p.colHeight)
|
||||
} else {
|
||||
focusStyle = p.common.Styles.ColumnFocused
|
||||
focusedStyle = p.common.Styles.ColumnFocused.Width(p.colWidth).Height(p.colHeight)
|
||||
}
|
||||
var picker, area string
|
||||
blurredStyle = p.common.Styles.ColumnBlurred.Width(p.colWidth).Height(p.colHeight)
|
||||
// var picker, area string
|
||||
var area string
|
||||
if p.columnCursor == 0 {
|
||||
picker = focusStyle.Render(p.areaPicker.View())
|
||||
area = p.common.Styles.ColumnBlurred.Render(p.areas[p.area].View())
|
||||
// picker = focusedStyle.Render(p.areaPicker.View())
|
||||
area = blurredStyle.Render(p.areas[p.area].View())
|
||||
} else {
|
||||
picker = p.common.Styles.ColumnBlurred.Render(p.areaPicker.View())
|
||||
area = focusStyle.Render(p.areas[p.area].View())
|
||||
// picker = blurredStyle.Render(p.areaPicker.View())
|
||||
area = focusedStyle.Render(p.areas[p.area].View())
|
||||
|
||||
}
|
||||
|
||||
return lipgloss.JoinHorizontal(
|
||||
lipgloss.Center,
|
||||
picker,
|
||||
area,
|
||||
)
|
||||
if p.task.Uuid != "" {
|
||||
area = lipgloss.JoinHorizontal(
|
||||
lipgloss.Top,
|
||||
area,
|
||||
p.common.Styles.ColumnFocused.Render(p.common.TW.GetInformation(&p.task)),
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// return lipgloss.Place(p.common.Width(), p.common.Height(), lipgloss.Center, lipgloss.Center, lipgloss.JoinHorizontal(
|
||||
// lipgloss.Center,
|
||||
// picker,
|
||||
// area,
|
||||
// ))
|
||||
return lipgloss.Place(p.common.Width(), p.common.Height(), lipgloss.Center, lipgloss.Center, area)
|
||||
}
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "io"
|
||||
// "log/slog"
|
||||
// "strings"
|
||||
// "tasksquire/common"
|
||||
// "tasksquire/taskwarrior"
|
||||
// "time"
|
||||
|
||||
// "github.com/charmbracelet/bubbles/list"
|
||||
// "github.com/charmbracelet/bubbles/textinput"
|
||||
// tea "github.com/charmbracelet/bubbletea"
|
||||
// "github.com/charmbracelet/huh"
|
||||
// "github.com/charmbracelet/lipgloss"
|
||||
// )
|
||||
|
||||
// type Field int
|
||||
|
||||
// const (
|
||||
// FieldDescription Field = iota
|
||||
// FieldPriority
|
||||
// FieldProject
|
||||
// FieldNewProject
|
||||
// FieldTags
|
||||
// FieldNewTags
|
||||
// FieldDue
|
||||
// FieldScheduled
|
||||
// FieldWait
|
||||
// FieldUntil
|
||||
// )
|
||||
|
||||
// type column int
|
||||
|
||||
// const (
|
||||
// column1 column = iota
|
||||
// column2
|
||||
// column3
|
||||
// )
|
||||
|
||||
// func changeColumn(c column) tea.Cmd {
|
||||
// return func() tea.Msg {
|
||||
// return changeColumnMsg(c)
|
||||
// }
|
||||
// }
|
||||
|
||||
// type changeColumnMsg column
|
||||
|
||||
// type mode int
|
||||
|
||||
// const (
|
||||
// modeNormal mode = iota
|
||||
// modeInsert
|
||||
// modeAddTag
|
||||
// modeAddProject
|
||||
// )
|
||||
|
||||
// type TaskEditorPage struct {
|
||||
// common *common.Common
|
||||
// task taskwarrior.Task
|
||||
// areaList tea.Model
|
||||
// mode mode
|
||||
// statusline tea.Model
|
||||
|
||||
// // TODO: rework support for adding tags and projects
|
||||
// additionalTags string
|
||||
// additionalProject string
|
||||
|
||||
// columnCursor int
|
||||
// columns []tea.Model
|
||||
|
||||
// areas map[area][]tea.Model
|
||||
// selectedArea area
|
||||
// }
|
||||
|
||||
// type TaskEditorKeys struct {
|
||||
// Quit key.Binding
|
||||
// Up key.Binding
|
||||
// Down key.Binding
|
||||
// Select key.Binding
|
||||
// ToggleFocus key.Binding
|
||||
// }
|
||||
|
||||
// func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditorPage {
|
||||
// p := &TaskEditorPage{
|
||||
// common: common,
|
||||
// task: task,
|
||||
// }
|
||||
|
||||
// if p.task.Uuid == "" {
|
||||
// p.mode = modeInsert
|
||||
// } else {
|
||||
// p.mode = modeNormal
|
||||
// }
|
||||
|
||||
// areaItems := []list.Item{
|
||||
// item("Task"),
|
||||
// item("Tags"),
|
||||
// item("Time"),
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// p.statusline = NewStatusLine(common, p.mode)
|
||||
|
||||
// return p
|
||||
// }
|
||||
|
||||
type area int
|
||||
|
||||
const (
|
||||
areaTask area = iota
|
||||
areaTags
|
||||
areaTime
|
||||
)
|
||||
type area interface {
|
||||
tea.Model
|
||||
SetCursor(c int)
|
||||
}
|
||||
|
||||
type areaPicker struct {
|
||||
common *common.Common
|
||||
@ -383,17 +304,18 @@ func NewAreaPicker(common *common.Common, items []string) *areaPicker {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *areaPicker) Area() area {
|
||||
switch a.list.SelectedItem() {
|
||||
case item("Task"):
|
||||
return areaTask
|
||||
case item("Tags"):
|
||||
return areaTags
|
||||
case item("Dates"):
|
||||
return areaTime
|
||||
default:
|
||||
return areaTask
|
||||
}
|
||||
func (a *areaPicker) Area() int {
|
||||
// switch a.list.SelectedItem() {
|
||||
// case item("Task"):
|
||||
// return areaTask
|
||||
// case item("Tags"):
|
||||
// return areaTags
|
||||
// case item("Dates"):
|
||||
// return areaTime
|
||||
// default:
|
||||
// return areaTask
|
||||
// }
|
||||
return 0
|
||||
}
|
||||
|
||||
func (a *areaPicker) Init() tea.Cmd {
|
||||
@ -429,50 +351,138 @@ type taskEdit struct {
|
||||
cursor int
|
||||
|
||||
newProjectName *string
|
||||
newAnnotation *string
|
||||
udaValues map[string]*string
|
||||
}
|
||||
|
||||
func NewTaskEdit(common *common.Common, description *string, priority *string, project *string, priorityOptions []string, projectOptions []string) *taskEdit {
|
||||
func NewTaskEdit(com *common.Common, task *taskwarrior.Task) *taskEdit {
|
||||
newProject := ""
|
||||
projectOptions := append([]string{"(none)"}, com.TW.GetProjects()...)
|
||||
if task.Project == "" {
|
||||
task.Project = "(none)"
|
||||
}
|
||||
|
||||
defaultKeymap := huh.NewDefaultKeyMap()
|
||||
|
||||
t := taskEdit{
|
||||
common: common,
|
||||
fields: []huh.Field{
|
||||
huh.NewInput().
|
||||
Title("Task").
|
||||
Value(description).
|
||||
Validate(func(desc string) error {
|
||||
if desc == "" {
|
||||
return fmt.Errorf("task description is required")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
fields := []huh.Field{
|
||||
huh.NewInput().
|
||||
Title("Task").
|
||||
Value(&task.Description).
|
||||
Validate(func(desc string) error {
|
||||
if desc == "" {
|
||||
return fmt.Errorf("task description is required")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(com.Styles.Form),
|
||||
|
||||
huh.NewSelect[string]().
|
||||
Options(huh.NewOptions(projectOptions...)...).
|
||||
Title("Project").
|
||||
Value(&task.Project).
|
||||
WithKeyMap(defaultKeymap).
|
||||
WithTheme(com.Styles.Form),
|
||||
|
||||
huh.NewInput().
|
||||
Title("New Project").
|
||||
Value(&newProject).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(com.Styles.Form),
|
||||
}
|
||||
|
||||
udaValues := make(map[string]*string)
|
||||
for _, uda := range com.Udas {
|
||||
switch uda.Type {
|
||||
case taskwarrior.UdaTypeNumeric:
|
||||
val := ""
|
||||
udaValues[uda.Name] = &val
|
||||
fields = append(fields, huh.NewInput().
|
||||
Title(uda.Label).
|
||||
Value(udaValues[uda.Name]).
|
||||
Validate(taskwarrior.ValidateNumeric).
|
||||
Inline(true).
|
||||
WithTheme(common.Styles.Form),
|
||||
Prompt(": ").
|
||||
WithTheme(com.Styles.Form))
|
||||
case taskwarrior.UdaTypeString:
|
||||
if len(uda.Values) > 0 {
|
||||
var val string
|
||||
values := make([]string, len(uda.Values))
|
||||
for i, v := range uda.Values {
|
||||
values[i] = v
|
||||
if v == "" {
|
||||
values[i] = "(none)"
|
||||
}
|
||||
if v == uda.Default {
|
||||
val = values[i]
|
||||
}
|
||||
}
|
||||
if val == "" {
|
||||
val = values[0]
|
||||
}
|
||||
if v, ok := task.Udas[uda.Name]; ok {
|
||||
//TODO: handle uda types correctly
|
||||
val = v.(string)
|
||||
}
|
||||
udaValues[uda.Name] = &val
|
||||
|
||||
huh.NewSelect[string]().
|
||||
Options(huh.NewOptions(priorityOptions...)...).
|
||||
Title("Priority").
|
||||
Key("priority").
|
||||
Value(priority).
|
||||
WithKeyMap(defaultKeymap).
|
||||
WithTheme(common.Styles.Form),
|
||||
fields = append(fields, huh.NewSelect[string]().
|
||||
Options(huh.NewOptions(values...)...).
|
||||
Title(uda.Label).
|
||||
Value(udaValues[uda.Name]).
|
||||
WithKeyMap(defaultKeymap).
|
||||
WithTheme(com.Styles.Form))
|
||||
} else {
|
||||
val := ""
|
||||
udaValues[uda.Name] = &val
|
||||
fields = append(fields, huh.NewInput().
|
||||
Title(uda.Label).
|
||||
Value(udaValues[uda.Name]).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(com.Styles.Form))
|
||||
}
|
||||
case taskwarrior.UdaTypeDate:
|
||||
val := ""
|
||||
udaValues[uda.Name] = &val
|
||||
fields = append(fields, huh.NewInput().
|
||||
Title(uda.Label).
|
||||
Value(udaValues[uda.Name]).
|
||||
Validate(taskwarrior.ValidateDate).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(com.Styles.Form))
|
||||
case taskwarrior.UdaTypeDuration:
|
||||
val := ""
|
||||
udaValues[uda.Name] = &val
|
||||
fields = append(fields, huh.NewInput().
|
||||
Title(uda.Label).
|
||||
Value(udaValues[uda.Name]).
|
||||
Validate(taskwarrior.ValidateDuration).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(com.Styles.Form))
|
||||
}
|
||||
}
|
||||
|
||||
huh.NewSelect[string]().
|
||||
Options(huh.NewOptions(projectOptions...)...).
|
||||
Title("Project").
|
||||
Value(project).
|
||||
WithKeyMap(defaultKeymap).
|
||||
WithTheme(common.Styles.Form),
|
||||
newAnnotation := ""
|
||||
fields = append(fields, huh.NewInput().
|
||||
Title("New Annotation").
|
||||
Value(&newAnnotation).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(com.Styles.Form))
|
||||
|
||||
huh.NewInput().
|
||||
Title("New Project").
|
||||
Value(&newProject).
|
||||
WithTheme(common.Styles.Form),
|
||||
},
|
||||
t := taskEdit{
|
||||
common: com,
|
||||
fields: fields,
|
||||
|
||||
udaValues: udaValues,
|
||||
|
||||
newProjectName: &newProject,
|
||||
newAnnotation: &newAnnotation,
|
||||
}
|
||||
|
||||
t.fields[0].Focus()
|
||||
@ -480,6 +490,16 @@ func NewTaskEdit(common *common.Common, description *string, priority *string, p
|
||||
return &t
|
||||
}
|
||||
|
||||
func (t *taskEdit) SetCursor(c int) {
|
||||
t.fields[t.cursor].Blur()
|
||||
if c < 0 {
|
||||
t.cursor = len(t.fields) - 1
|
||||
} else {
|
||||
t.cursor = c
|
||||
}
|
||||
t.fields[t.cursor].Focus()
|
||||
}
|
||||
|
||||
func (t *taskEdit) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
@ -515,6 +535,9 @@ func (t *taskEdit) View() string {
|
||||
views := make([]string, len(t.fields))
|
||||
for i, field := range t.fields {
|
||||
views[i] = field.View()
|
||||
if i < len(t.fields)-1 {
|
||||
views[i] += "\n"
|
||||
}
|
||||
}
|
||||
return lipgloss.JoinVertical(
|
||||
lipgloss.Left,
|
||||
@ -551,16 +574,25 @@ func NewTagEdit(common *common.Common, selected *[]string, options []string) *ta
|
||||
Title("New Tags").
|
||||
Value(&newTags).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(common.Styles.Form),
|
||||
},
|
||||
newTagsValue: &newTags,
|
||||
}
|
||||
|
||||
t.fields[0].Focus()
|
||||
|
||||
return &t
|
||||
}
|
||||
|
||||
func (t *tagEdit) SetCursor(c int) {
|
||||
t.fields[t.cursor].Blur()
|
||||
if c < 0 {
|
||||
t.cursor = len(t.fields) - 1
|
||||
} else {
|
||||
t.cursor = c
|
||||
}
|
||||
t.fields[t.cursor].Focus()
|
||||
}
|
||||
|
||||
func (t *tagEdit) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
@ -619,33 +651,45 @@ func NewTimeEdit(common *common.Common, due *string, scheduled *string, wait *st
|
||||
Value(due).
|
||||
Validate(taskwarrior.ValidateDate).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(common.Styles.Form),
|
||||
huh.NewInput().
|
||||
Title("Scheduled").
|
||||
Value(scheduled).
|
||||
Validate(taskwarrior.ValidateDate).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(common.Styles.Form),
|
||||
huh.NewInput().
|
||||
Title("Wait").
|
||||
Value(wait).
|
||||
Validate(taskwarrior.ValidateDate).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(common.Styles.Form),
|
||||
huh.NewInput().
|
||||
Title("Until").
|
||||
Value(until).
|
||||
Validate(taskwarrior.ValidateDate).
|
||||
Inline(true).
|
||||
Prompt(": ").
|
||||
WithTheme(common.Styles.Form),
|
||||
},
|
||||
}
|
||||
|
||||
t.fields[0].Focus()
|
||||
|
||||
return &t
|
||||
}
|
||||
|
||||
func (t *timeEdit) SetCursor(c int) {
|
||||
t.fields[t.cursor].Blur()
|
||||
if c < 0 {
|
||||
t.cursor = len(t.fields) - 1
|
||||
} else {
|
||||
t.cursor = c
|
||||
}
|
||||
t.fields[t.cursor].Focus()
|
||||
}
|
||||
|
||||
func (t *timeEdit) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
@ -687,6 +731,75 @@ func (t *timeEdit) View() string {
|
||||
)
|
||||
}
|
||||
|
||||
type detailsEdit struct {
|
||||
com *common.Common
|
||||
renderer *glamour.TermRenderer
|
||||
vp viewport.Model
|
||||
}
|
||||
|
||||
func NewDetailsEdit(com *common.Common, task *taskwarrior.Task) *detailsEdit {
|
||||
renderer, err := glamour.NewTermRenderer(
|
||||
// glamour.WithStandardStyle("light"),
|
||||
glamour.WithAutoStyle(),
|
||||
glamour.WithWordWrap(40),
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
vp := viewport.New(40, 30)
|
||||
d := detailsEdit{
|
||||
com: com,
|
||||
renderer: renderer,
|
||||
vp: vp,
|
||||
}
|
||||
|
||||
return &d
|
||||
}
|
||||
|
||||
func (d *detailsEdit) SetCursor(c int) {
|
||||
}
|
||||
|
||||
func (d *detailsEdit) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *detailsEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg.(type) {
|
||||
case nextFieldMsg:
|
||||
return d, nextArea()
|
||||
case prevFieldMsg:
|
||||
return d, prevArea()
|
||||
default:
|
||||
var cmd tea.Cmd
|
||||
d.vp, cmd = d.vp.Update(msg)
|
||||
return d, cmd
|
||||
}
|
||||
}
|
||||
|
||||
func (d *detailsEdit) View() string {
|
||||
dtls := `
|
||||
# Cool Details!
|
||||
## Things I need
|
||||
- [ ] A thing
|
||||
- [x] Done thing
|
||||
|
||||
## People
|
||||
- pe1
|
||||
- pe2
|
||||
`
|
||||
|
||||
details, err := d.renderer.Render(dtls)
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
return "Could not parse markdown"
|
||||
}
|
||||
|
||||
d.vp.SetContent(details)
|
||||
return d.vp.View()
|
||||
}
|
||||
|
||||
// func (p *TaskEditorPage) SetSize(width, height int) {
|
||||
// p.common.SetSize(width, height)
|
||||
// }
|
||||
@ -813,26 +926,36 @@ func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
|
||||
if p.task.Project == "(none)" {
|
||||
p.task.Project = ""
|
||||
}
|
||||
if p.task.Priority == "(none)" {
|
||||
p.task.Priority = ""
|
||||
|
||||
for _, uda := range p.common.Udas {
|
||||
if val, ok := p.areas[0].(*taskEdit).udaValues[uda.Name]; ok {
|
||||
if *val == "(none)" {
|
||||
*val = ""
|
||||
}
|
||||
p.task.Udas[uda.Name] = *val
|
||||
}
|
||||
}
|
||||
|
||||
if *(p.areas[areaTask].(*taskEdit).newProjectName) != "" {
|
||||
p.task.Project = *p.areas[areaTask].(*taskEdit).newProjectName
|
||||
if *(p.areas[0].(*taskEdit).newProjectName) != "" {
|
||||
p.task.Project = *p.areas[0].(*taskEdit).newProjectName
|
||||
}
|
||||
|
||||
if *(p.areas[areaTags].(*tagEdit).newTagsValue) != "" {
|
||||
newTags := strings.Split(*p.areas[areaTags].(*tagEdit).newTagsValue, " ")
|
||||
if *(p.areas[1].(*tagEdit).newTagsValue) != "" {
|
||||
newTags := strings.Split(*p.areas[1].(*tagEdit).newTagsValue, " ")
|
||||
if len(newTags) > 0 {
|
||||
p.task.Tags = append(p.task.Tags, newTags...)
|
||||
}
|
||||
}
|
||||
|
||||
// if p.additionalProject != "" {
|
||||
// p.task.Project = p.additionalProject
|
||||
// }
|
||||
// tags := p.form.Get("tags").([]string)
|
||||
// p.task.Tags = tags
|
||||
if *(p.areas[0].(*taskEdit).newAnnotation) != "" {
|
||||
p.task.Annotations = append(p.task.Annotations, taskwarrior.Annotation{
|
||||
Entry: time.Now().Format("20060102T150405Z"),
|
||||
Description: *(p.areas[0].(*taskEdit).newAnnotation),
|
||||
})
|
||||
|
||||
// p.common.TW.AddTaskAnnotation(p.task.Uuid, *p.areas[0].(*taskEdit).newAnnotation)
|
||||
}
|
||||
|
||||
p.common.TW.ImportTask(&p.task)
|
||||
return UpdatedTasksMsg{}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package taskwarrior
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
@ -13,6 +14,23 @@ const (
|
||||
dtformat = "20060102T150405Z"
|
||||
)
|
||||
|
||||
type UdaType string
|
||||
|
||||
const (
|
||||
UdaTypeString UdaType = "string"
|
||||
UdaTypeDate UdaType = "date"
|
||||
UdaTypeNumeric UdaType = "numeric"
|
||||
UdaTypeDuration UdaType = "duration"
|
||||
)
|
||||
|
||||
type Uda struct {
|
||||
Name string
|
||||
Type UdaType
|
||||
Label string
|
||||
Values []string
|
||||
Default string
|
||||
}
|
||||
|
||||
type Annotation struct {
|
||||
Entry string `json:"entry,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
@ -22,12 +40,14 @@ func (a Annotation) String() string {
|
||||
return fmt.Sprintf("%s %s", a.Entry, a.Description)
|
||||
}
|
||||
|
||||
type Tasks []*Task
|
||||
|
||||
type Task struct {
|
||||
Id int64 `json:"id,omitempty"`
|
||||
Uuid string `json:"uuid,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Project string `json:"project"`
|
||||
Priority string `json:"priority"`
|
||||
Id int64 `json:"id,omitempty"`
|
||||
Uuid string `json:"uuid,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Project string `json:"project"`
|
||||
// Priority string `json:"priority"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
VirtualTags []string `json:"-"`
|
||||
@ -114,8 +134,8 @@ func (t *Task) GetString(fieldWFormat string) string {
|
||||
}
|
||||
return t.Project
|
||||
|
||||
case "priority":
|
||||
return t.Priority
|
||||
// case "priority":
|
||||
// return t.Priority
|
||||
|
||||
case "status":
|
||||
return t.Status
|
||||
@ -186,6 +206,7 @@ func (t *Task) GetString(fieldWFormat string) string {
|
||||
return t.Recur
|
||||
|
||||
default:
|
||||
// TODO: format according to UDA type
|
||||
if val, ok := t.Udas[field]; ok {
|
||||
if strVal, ok := val.(string); ok {
|
||||
return strVal
|
||||
@ -230,7 +251,69 @@ func (t *Task) RemoveTag(tag string) {
|
||||
}
|
||||
}
|
||||
|
||||
type Tasks []*Task
|
||||
func (t *Task) UnmarshalJSON(data []byte) error {
|
||||
type Alias Task
|
||||
task := Alias{}
|
||||
|
||||
if err := json.Unmarshal(data, &task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = Task(task)
|
||||
|
||||
m := make(map[string]any)
|
||||
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(m, "id")
|
||||
delete(m, "uuid")
|
||||
delete(m, "description")
|
||||
delete(m, "project")
|
||||
// delete(m, "priority")
|
||||
delete(m, "status")
|
||||
delete(m, "tags")
|
||||
delete(m, "depends")
|
||||
delete(m, "urgency")
|
||||
delete(m, "parent")
|
||||
delete(m, "due")
|
||||
delete(m, "wait")
|
||||
delete(m, "scheduled")
|
||||
delete(m, "until")
|
||||
delete(m, "start")
|
||||
delete(m, "end")
|
||||
delete(m, "entry")
|
||||
delete(m, "modified")
|
||||
delete(m, "recur")
|
||||
delete(m, "annotations")
|
||||
t.Udas = m
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) MarshalJSON() ([]byte, error) {
|
||||
type Alias Task
|
||||
task := Alias(*t)
|
||||
|
||||
knownFields, err := json.Marshal(task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var knownMap map[string]any
|
||||
if err := json.Unmarshal(knownFields, &knownMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, value := range t.Udas {
|
||||
if value != nil && value != "" {
|
||||
knownMap[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(knownMap)
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
Name string
|
||||
@ -418,3 +501,15 @@ func ValidateDate(s string) error {
|
||||
|
||||
return fmt.Errorf("invalid date")
|
||||
}
|
||||
|
||||
func ValidateNumeric(s string) error {
|
||||
if _, err := strconv.ParseFloat(s, 64); err != nil {
|
||||
return fmt.Errorf("invalid number")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateDuration(s string) error {
|
||||
// TODO: implement duration validation
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -89,15 +89,17 @@ type TaskWarrior interface {
|
||||
GetReport(report string) *Report
|
||||
GetReports() Reports
|
||||
|
||||
GetUdas() []string
|
||||
GetUdas() []Uda
|
||||
|
||||
GetTasks(report *Report, filter ...string) Tasks
|
||||
AddTask(task *Task) error
|
||||
// AddTask(task *Task) error
|
||||
ImportTask(task *Task)
|
||||
SetTaskDone(task *Task)
|
||||
DeleteTask(task *Task)
|
||||
StartTask(task *Task)
|
||||
StopTask(task *Task)
|
||||
GetInformation(task *Task) string
|
||||
AddTaskAnnotation(uuid string, annotation string)
|
||||
|
||||
Undo()
|
||||
}
|
||||
@ -171,15 +173,7 @@ func (ts *TaskSquire) GetTasks(report *Report, filter ...string) Tasks {
|
||||
return nil
|
||||
}
|
||||
|
||||
unstructuredTasks := make([]map[string]any, 0)
|
||||
err = json.Unmarshal(output, &unstructuredTasks)
|
||||
if err != nil {
|
||||
slog.Error("Failed unmarshalling tasks:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, task := range tasks {
|
||||
task.Udas = unstructuredTasks[i]
|
||||
for _, task := range tasks {
|
||||
if task.Depends != nil && len(task.Depends) > 0 {
|
||||
ids := make([]string, len(task.Depends))
|
||||
for i, dependUuid := range task.Depends {
|
||||
@ -325,7 +319,7 @@ func (ts *TaskSquire) GetReports() Reports {
|
||||
return ts.reports
|
||||
}
|
||||
|
||||
func (ts *TaskSquire) GetUdas() []string {
|
||||
func (ts *TaskSquire) GetUdas() []Uda {
|
||||
ts.mutex.Lock()
|
||||
defer ts.mutex.Unlock()
|
||||
|
||||
@ -336,9 +330,27 @@ func (ts *TaskSquire) GetUdas() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
udas := make([]string, 0)
|
||||
udas := make([]Uda, 0)
|
||||
for _, uda := range strings.Split(string(output), "\n") {
|
||||
if uda != "" {
|
||||
udatype := UdaType(ts.config.Get(fmt.Sprintf("uda.%s.type", uda)))
|
||||
if udatype == "" {
|
||||
slog.Error(fmt.Sprintf("UDA type not found: %s", uda))
|
||||
continue
|
||||
}
|
||||
|
||||
label := ts.config.Get(fmt.Sprintf("uda.%s.label", uda))
|
||||
values := strings.Split(ts.config.Get(fmt.Sprintf("uda.%s.values", uda)), ",")
|
||||
def := ts.config.Get(fmt.Sprintf("uda.%s.default", uda))
|
||||
|
||||
uda := Uda{
|
||||
Name: uda,
|
||||
Label: label,
|
||||
Type: udatype,
|
||||
Values: values,
|
||||
Default: def,
|
||||
}
|
||||
|
||||
udas = append(udas, uda)
|
||||
}
|
||||
}
|
||||
@ -367,42 +379,42 @@ func (ts *TaskSquire) SetContext(context *Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *TaskSquire) AddTask(task *Task) error {
|
||||
ts.mutex.Lock()
|
||||
defer ts.mutex.Unlock()
|
||||
// func (ts *TaskSquire) AddTask(task *Task) error {
|
||||
// ts.mutex.Lock()
|
||||
// defer ts.mutex.Unlock()
|
||||
|
||||
addArgs := []string{"add"}
|
||||
// addArgs := []string{"add"}
|
||||
|
||||
if task.Description == "" {
|
||||
slog.Error("Task description is required")
|
||||
return nil
|
||||
} else {
|
||||
addArgs = append(addArgs, task.Description)
|
||||
}
|
||||
if task.Priority != "" && task.Priority != "(none)" {
|
||||
addArgs = append(addArgs, fmt.Sprintf("priority:%s", task.Priority))
|
||||
}
|
||||
if task.Project != "" && task.Project != "(none)" {
|
||||
addArgs = append(addArgs, fmt.Sprintf("project:%s", task.Project))
|
||||
}
|
||||
if task.Tags != nil {
|
||||
for _, tag := range task.Tags {
|
||||
addArgs = append(addArgs, fmt.Sprintf("+%s", tag))
|
||||
}
|
||||
}
|
||||
if task.Due != "" {
|
||||
addArgs = append(addArgs, fmt.Sprintf("due:%s", task.Due))
|
||||
}
|
||||
// if task.Description == "" {
|
||||
// slog.Error("Task description is required")
|
||||
// return nil
|
||||
// } else {
|
||||
// addArgs = append(addArgs, task.Description)
|
||||
// }
|
||||
// if task.Priority != "" && task.Priority != "(none)" {
|
||||
// addArgs = append(addArgs, fmt.Sprintf("priority:%s", task.Priority))
|
||||
// }
|
||||
// if task.Project != "" && task.Project != "(none)" {
|
||||
// addArgs = append(addArgs, fmt.Sprintf("project:%s", task.Project))
|
||||
// }
|
||||
// if task.Tags != nil {
|
||||
// for _, tag := range task.Tags {
|
||||
// addArgs = append(addArgs, fmt.Sprintf("+%s", tag))
|
||||
// }
|
||||
// }
|
||||
// if task.Due != "" {
|
||||
// addArgs = append(addArgs, fmt.Sprintf("due:%s", task.Due))
|
||||
// }
|
||||
|
||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, addArgs...)...)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
slog.Error("Failed adding task:", err)
|
||||
}
|
||||
// cmd := exec.Command(twBinary, append(ts.defaultArgs, addArgs...)...)
|
||||
// err := cmd.Run()
|
||||
// if err != nil {
|
||||
// slog.Error("Failed adding task:", err)
|
||||
// }
|
||||
|
||||
// TODO remove error?
|
||||
return nil
|
||||
}
|
||||
// // TODO remove error?
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// TODO error handling
|
||||
func (ts *TaskSquire) ImportTask(task *Task) {
|
||||
@ -410,7 +422,6 @@ func (ts *TaskSquire) ImportTask(task *Task) {
|
||||
defer ts.mutex.Unlock()
|
||||
|
||||
tasks, err := json.Marshal(Tasks{task})
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Failed marshalling task:", err)
|
||||
}
|
||||
@ -479,6 +490,31 @@ func (ts *TaskSquire) StopTask(task *Task) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TaskSquire) GetInformation(task *Task) string {
|
||||
ts.mutex.Lock()
|
||||
defer ts.mutex.Unlock()
|
||||
|
||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{fmt.Sprintf("uuid:%s", task.Uuid), "information"}...)...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
slog.Error("Failed getting task information:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(output)
|
||||
}
|
||||
|
||||
func (ts *TaskSquire) AddTaskAnnotation(uuid string, annotation string) {
|
||||
ts.mutex.Lock()
|
||||
defer ts.mutex.Unlock()
|
||||
|
||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{uuid, "annotate", annotation}...)...)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
slog.Error("Failed adding annotation:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TaskSquire) extractConfig() *TWConfig {
|
||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"_show"}...)...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
Binary file not shown.
14
test/taskrc
14
test/taskrc
@ -1,13 +1,25 @@
|
||||
include light-256.theme
|
||||
|
||||
uda.priority.values=H,M,,L
|
||||
|
||||
context.test.read=+test
|
||||
context.test.write=+test
|
||||
context.home.read=+home
|
||||
context.home.write=+home
|
||||
|
||||
uda.testuda.type=string
|
||||
uda.testuda.label=testuda
|
||||
uda.testuda.label=Testuda
|
||||
uda.testuda.values=eins,zwei,drei
|
||||
uda.testuda.default=eins
|
||||
|
||||
uda.testuda2.type=numeric
|
||||
uda.testuda2.label=TESTUDA2
|
||||
|
||||
uda.testuda3.type=date
|
||||
uda.testuda3.label=Ttttuda
|
||||
|
||||
uda.testuda4.type=duration
|
||||
uda.testuda4.label=TtttudaDURUD
|
||||
|
||||
report.next.columns=id,testuda,start.age,entry.age,depends,priority,project,tags,recur,scheduled.countdown,due.relative,until.remaining,description,urgency
|
||||
report.next.context=1
|
||||
|
||||
Reference in New Issue
Block a user