Compare commits

...

5 Commits

Author SHA1 Message Date
2e33893e29 Merge branch 'feat/taskedit' into feat/time 2026-02-03 07:40:11 +01:00
46ce91196a Merge branch 'feat/task' into feat/time 2026-02-03 07:39:59 +01:00
70b6ee9bc7 Add picker to task edit 2026-02-02 20:43:08 +01:00
2baf3859fd Add tab bar 2026-02-02 19:47:18 +01:00
2940711b26 Make task details scrollable 2026-02-02 19:39:02 +01:00
5 changed files with 257 additions and 51 deletions

View File

@ -27,6 +27,10 @@ type Styles struct {
Form *huh.Theme Form *huh.Theme
TableStyle TableStyle TableStyle TableStyle
Tab lipgloss.Style
ActiveTab lipgloss.Style
TabBar lipgloss.Style
ColumnFocused lipgloss.Style ColumnFocused lipgloss.Style
ColumnBlurred lipgloss.Style ColumnBlurred lipgloss.Style
ColumnInsert lipgloss.Style ColumnInsert lipgloss.Style
@ -71,6 +75,19 @@ func NewStyles(config *taskwarrior.TWConfig) *Styles {
styles.Form = formTheme styles.Form = formTheme
styles.Tab = lipgloss.NewStyle().
Padding(0, 1).
Foreground(lipgloss.Color("240"))
styles.ActiveTab = styles.Tab.
Foreground(lipgloss.Color("252")).
Bold(true)
styles.TabBar = lipgloss.NewStyle().
Border(lipgloss.NormalBorder(), false, false, true, false).
BorderForeground(lipgloss.Color("240")).
MarginBottom(1)
styles.ColumnFocused = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1) styles.ColumnFocused = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1)
styles.ColumnBlurred = lipgloss.NewStyle().Border(lipgloss.HiddenBorder(), true).Padding(1) styles.ColumnBlurred = lipgloss.NewStyle().Border(lipgloss.HiddenBorder(), true).Padding(1)
styles.ColumnInsert = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1) styles.ColumnInsert = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1)

View File

@ -637,6 +637,11 @@ func (m *MultiSelect) GetValue() any {
return *m.value return *m.value
} }
// IsFiltering returns true if the multi-select is currently filtering.
func (m *MultiSelect) IsFiltering() bool {
return m.filtering
}
func min(a, b int) int { func min(a, b int) int {
if a < b { if a < b {
return a return a

View File

@ -37,6 +37,7 @@ type Picker struct {
title string title string
filterByDefault bool filterByDefault bool
baseItems []list.Item baseItems []list.Item
focused bool
} }
type PickerOption func(*Picker) type PickerOption func(*Picker)
@ -53,6 +54,24 @@ func WithOnCreate(onCreate func(string) tea.Cmd) PickerOption {
} }
} }
func (p *Picker) Focus() tea.Cmd {
p.focused = true
return nil
}
func (p *Picker) Blur() tea.Cmd {
p.focused = false
return nil
}
func (p *Picker) GetValue() string {
item := p.list.SelectedItem()
if item == nil {
return ""
}
return item.FilterValue()
}
func New( func New(
c *common.Common, c *common.Common,
title string, title string,
@ -82,6 +101,7 @@ func New(
itemProvider: itemProvider, itemProvider: itemProvider,
onSelect: onSelect, onSelect: onSelect,
title: title, title: title,
focused: true,
} }
if c.TW.GetConfig().Get("uda.tasksquire.picker.filter_by_default") == "yes" { if c.TW.GetConfig().Get("uda.tasksquire.picker.filter_by_default") == "yes" {
@ -92,6 +112,14 @@ func New(
opt(p) opt(p)
} }
if p.filterByDefault {
// Manually trigger filter mode on the list so it doesn't require a global key press
var cmd tea.Cmd
p.list, cmd = p.list.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'i'}})
// We can ignore the command here as it's likely just for blinking, which will happen on Init anyway
_ = cmd
}
p.Refresh() p.Refresh()
return p return p
@ -134,27 +162,26 @@ func (p *Picker) SetSize(width, height int) {
} }
func (p *Picker) Init() tea.Cmd { func (p *Picker) Init() tea.Cmd {
if p.filterByDefault {
return func() tea.Msg {
return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'i'}}
}
}
return nil return nil
} }
func (p *Picker) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (p *Picker) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if !p.focused {
return p, nil
}
var cmd tea.Cmd var cmd tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
// If filtering, let the list handle keys (including Enter to stop filtering) // If filtering, let the list handle keys (including Enter to stop filtering)
if p.list.FilterState() == list.Filtering { if p.list.FilterState() == list.Filtering {
if key.Matches(msg, p.common.Keymap.Ok) { // if key.Matches(msg, p.common.Keymap.Ok) {
items := p.list.VisibleItems() // items := p.list.VisibleItems()
if len(items) == 1 { // if len(items) == 1 {
return p, p.handleSelect(items[0]) // return p, p.handleSelect(items[0])
} // }
} // }
break // Pass to list.Update break // Pass to list.Update
} }
@ -189,7 +216,12 @@ func (p *Picker) handleSelect(item list.Item) tea.Cmd {
} }
func (p *Picker) View() string { func (p *Picker) View() string {
title := p.common.Styles.Form.Focused.Title.Render(p.title) var title string
if p.focused {
title = p.common.Styles.Form.Focused.Title.Render(p.title)
} else {
title = p.common.Styles.Form.Blurred.Title.Render(p.title)
}
return lipgloss.JoinVertical(lipgloss.Left, title, p.list.View()) return lipgloss.JoinVertical(lipgloss.Left, title, p.list.View())
} }

View File

@ -5,6 +5,7 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
) )
type MainPage struct { type MainPage struct {
@ -13,6 +14,9 @@ type MainPage struct {
taskPage common.Component taskPage common.Component
timePage common.Component timePage common.Component
currentTab int
width int
height int
} }
func NewMainPage(common *common.Common) *MainPage { func NewMainPage(common *common.Common) *MainPage {
@ -24,6 +28,7 @@ func NewMainPage(common *common.Common) *MainPage {
m.timePage = NewTimePage(common) m.timePage = NewTimePage(common)
m.activePage = m.taskPage m.activePage = m.taskPage
m.currentTab = 0
return m return m
} }
@ -37,17 +42,39 @@ func (m *MainPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.WindowSizeMsg: case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
m.common.SetSize(msg.Width, msg.Height) m.common.SetSize(msg.Width, msg.Height)
tabHeight := lipgloss.Height(m.renderTabBar())
contentHeight := msg.Height - tabHeight
if contentHeight < 0 {
contentHeight = 0
}
newMsg := tea.WindowSizeMsg{Width: msg.Width, Height: contentHeight}
activePage, cmd := m.activePage.Update(newMsg)
m.activePage = activePage.(common.Component)
return m, cmd
case tea.KeyMsg: case tea.KeyMsg:
// Only handle tab key for page switching when at the top level (no subpages active) // Only handle tab key for page switching when at the top level (no subpages active)
if key.Matches(msg, m.common.Keymap.Next) && !m.common.HasSubpages() { if key.Matches(msg, m.common.Keymap.Next) && !m.common.HasSubpages() {
if m.activePage == m.taskPage { if m.activePage == m.taskPage {
m.activePage = m.timePage m.activePage = m.timePage
m.currentTab = 1
} else { } else {
m.activePage = m.taskPage m.activePage = m.taskPage
m.currentTab = 0
} }
// Re-size the new active page just in case
m.activePage.SetSize(m.common.Width(), m.common.Height()) tabHeight := lipgloss.Height(m.renderTabBar())
contentHeight := m.height - tabHeight
if contentHeight < 0 {
contentHeight = 0
}
m.activePage.SetSize(m.width, contentHeight)
// Trigger a refresh/init on switch? Maybe not needed if we keep state. // Trigger a refresh/init on switch? Maybe not needed if we keep state.
// But we might want to refresh data. // But we might want to refresh data.
return m, m.activePage.Init() return m, m.activePage.Init()
@ -60,6 +87,22 @@ func (m *MainPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd return m, cmd
} }
func (m *MainPage) View() string { func (m *MainPage) renderTabBar() string {
return m.activePage.View() var tabs []string
headers := []string{"Tasks", "Time"}
for i, header := range headers {
style := m.common.Styles.Tab
if m.currentTab == i {
style = m.common.Styles.ActiveTab
}
tabs = append(tabs, style.Render(header))
}
row := lipgloss.JoinHorizontal(lipgloss.Top, tabs...)
return m.common.Styles.TabBar.Width(m.common.Width()).Render(row)
}
func (m *MainPage) View() string {
return lipgloss.JoinVertical(lipgloss.Left, m.renderTabBar(), m.activePage.View())
} }

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"tasksquire/components/input" "tasksquire/components/input"
"tasksquire/components/picker"
"tasksquire/components/timestampeditor" "tasksquire/components/timestampeditor"
"tasksquire/taskwarrior" "tasksquire/taskwarrior"
@ -41,6 +42,8 @@ type TaskEditorPage struct {
area int area int
areaPicker *areaPicker areaPicker *areaPicker
areas []area areas []area
infoViewport viewport.Model
} }
func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPage { func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPage {
@ -56,7 +59,7 @@ func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPag
tagOptions := p.common.TW.GetTags() tagOptions := p.common.TW.GetTags()
p.areas = []area{ p.areas = []area{
NewTaskEdit(p.common, &p.task), NewTaskEdit(p.common, &p.task, p.task.Uuid == ""),
NewTagEdit(p.common, &p.task.Tags, tagOptions), NewTagEdit(p.common, &p.task.Tags, tagOptions),
NewTimeEdit(p.common, &p.task.Due, &p.task.Scheduled, &p.task.Wait, &p.task.Until), NewTimeEdit(p.common, &p.task.Due, &p.task.Scheduled, &p.task.Wait, &p.task.Until),
NewDetailsEdit(p.common, &p.task), NewDetailsEdit(p.common, &p.task),
@ -68,6 +71,11 @@ func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPag
p.areaPicker = NewAreaPicker(com, []string{"Task", "Tags", "Dates"}) p.areaPicker = NewAreaPicker(com, []string{"Task", "Tags", "Dates"})
p.infoViewport = viewport.New(0, 0)
if p.task.Uuid != "" {
p.infoViewport.SetContent(p.common.TW.GetInformation(&p.task))
}
p.columnCursor = 1 p.columnCursor = 1
if p.task.Uuid == "" { if p.task.Uuid == "" {
p.mode = modeInsert p.mode = modeInsert
@ -94,10 +102,20 @@ func (p *TaskEditorPage) SetSize(width, height int) {
} else { } else {
p.colHeight = height - p.common.Styles.ColumnFocused.GetVerticalFrameSize() p.colHeight = height - p.common.Styles.ColumnFocused.GetVerticalFrameSize()
} }
p.infoViewport.Width = width - p.colWidth - p.common.Styles.ColumnFocused.GetHorizontalFrameSize()*2 - 5
if p.infoViewport.Width < 0 {
p.infoViewport.Width = 0
}
p.infoViewport.Height = p.colHeight
} }
func (p *TaskEditorPage) Init() tea.Cmd { func (p *TaskEditorPage) Init() tea.Cmd {
return nil var cmds []tea.Cmd
for _, a := range p.areas {
cmds = append(cmds, a.Init())
}
return tea.Batch(cmds...)
} }
func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@ -110,12 +128,20 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
p.mode = mode(msg) p.mode = mode(msg)
case prevColumnMsg: case prevColumnMsg:
p.columnCursor-- p.columnCursor--
maxCols := 2
if p.task.Uuid != "" {
maxCols = 3
}
if p.columnCursor < 0 { if p.columnCursor < 0 {
p.columnCursor = len(p.areas) - 1 p.columnCursor = maxCols - 1
} }
case nextColumnMsg: case nextColumnMsg:
p.columnCursor++ p.columnCursor++
if p.columnCursor > len(p.areas)-1 { maxCols := 2
if p.task.Uuid != "" {
maxCols = 3
}
if p.columnCursor >= maxCols {
p.columnCursor = 0 p.columnCursor = 0
} }
case prevAreaMsg: case prevAreaMsg:
@ -166,20 +192,26 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
picker, cmd := p.areaPicker.Update(msg) picker, cmd := p.areaPicker.Update(msg)
p.areaPicker = picker.(*areaPicker) p.areaPicker = picker.(*areaPicker)
return p, cmd return p, cmd
} else { } else if p.columnCursor == 1 {
model, cmd := p.areas[p.area].Update(prevFieldMsg{}) model, cmd := p.areas[p.area].Update(prevFieldMsg{})
p.areas[p.area] = model.(area) p.areas[p.area] = model.(area)
return p, cmd return p, cmd
} else if p.columnCursor == 2 {
p.infoViewport.LineUp(1)
return p, nil
} }
case key.Matches(msg, p.common.Keymap.Down): case key.Matches(msg, p.common.Keymap.Down):
if p.columnCursor == 0 { if p.columnCursor == 0 {
picker, cmd := p.areaPicker.Update(msg) picker, cmd := p.areaPicker.Update(msg)
p.areaPicker = picker.(*areaPicker) p.areaPicker = picker.(*areaPicker)
return p, cmd return p, cmd
} else { } else if p.columnCursor == 1 {
model, cmd := p.areas[p.area].Update(nextFieldMsg{}) model, cmd := p.areas[p.area].Update(nextFieldMsg{})
p.areas[p.area] = model.(area) p.areas[p.area] = model.(area)
return p, cmd return p, cmd
} else if p.columnCursor == 2 {
p.infoViewport.LineDown(1)
return p, nil
} }
} }
} }
@ -212,25 +244,31 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
picker, cmd := p.areaPicker.Update(msg) picker, cmd := p.areaPicker.Update(msg)
p.areaPicker = picker.(*areaPicker) p.areaPicker = picker.(*areaPicker)
return p, cmd return p, cmd
} else { } else if p.columnCursor == 1 {
model, cmd := p.areas[p.area].Update(prevFieldMsg{}) model, cmd := p.areas[p.area].Update(prevFieldMsg{})
p.areas[p.area] = model.(area) p.areas[p.area] = model.(area)
return p, cmd return p, cmd
} }
return p, nil
case key.Matches(msg, p.common.Keymap.Next): case key.Matches(msg, p.common.Keymap.Next):
if p.columnCursor == 0 { if p.columnCursor == 0 {
picker, cmd := p.areaPicker.Update(msg) picker, cmd := p.areaPicker.Update(msg)
p.areaPicker = picker.(*areaPicker) p.areaPicker = picker.(*areaPicker)
return p, cmd return p, cmd
} else { } else if p.columnCursor == 1 {
model, cmd := p.areas[p.area].Update(nextFieldMsg{}) model, cmd := p.areas[p.area].Update(nextFieldMsg{})
p.areas[p.area] = model.(area) p.areas[p.area] = model.(area)
return p, cmd return p, cmd
} }
return p, nil
case key.Matches(msg, p.common.Keymap.Ok): case key.Matches(msg, p.common.Keymap.Ok):
isFiltering := p.areas[p.area].IsFiltering()
model, cmd := p.areas[p.area].Update(msg) model, cmd := p.areas[p.area].Update(msg)
if p.area != 3 { if p.area != 3 {
p.areas[p.area] = model.(area) p.areas[p.area] = model.(area)
if isFiltering {
return p, cmd
}
return p, tea.Batch(cmd, nextField()) return p, tea.Batch(cmd, nextField())
} }
return p, cmd return p, cmd
@ -241,6 +279,10 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
picker, cmd := p.areaPicker.Update(msg) picker, cmd := p.areaPicker.Update(msg)
p.areaPicker = picker.(*areaPicker) p.areaPicker = picker.(*areaPicker)
return p, cmd return p, cmd
} else if p.columnCursor == 2 {
var cmd tea.Cmd
p.infoViewport, cmd = p.infoViewport.Update(msg)
return p, cmd
} else { } else {
model, cmd := p.areas[p.area].Update(msg) model, cmd := p.areas[p.area].Update(msg)
p.areas[p.area] = model.(area) p.areas[p.area] = model.(area)
@ -253,29 +295,31 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (p *TaskEditorPage) View() string { func (p *TaskEditorPage) View() string {
var focusedStyle, blurredStyle lipgloss.Style var focusedStyle, blurredStyle lipgloss.Style
if p.mode == modeInsert { if p.mode == modeInsert {
focusedStyle = p.common.Styles.ColumnInsert.Width(p.colWidth).Height(p.colHeight) focusedStyle = p.common.Styles.ColumnInsert
} else { } else {
focusedStyle = p.common.Styles.ColumnFocused.Width(p.colWidth).Height(p.colHeight) focusedStyle = p.common.Styles.ColumnFocused
} }
blurredStyle = p.common.Styles.ColumnBlurred.Width(p.colWidth).Height(p.colHeight) blurredStyle = p.common.Styles.ColumnBlurred
// var picker, area string
var area string
if p.columnCursor == 0 {
// picker = focusedStyle.Render(p.areaPicker.View())
area = blurredStyle.Render(p.areas[p.area].View())
} else {
// picker = blurredStyle.Render(p.areaPicker.View())
area = focusedStyle.Render(p.areas[p.area].View())
var area string
if p.columnCursor == 1 {
area = focusedStyle.Copy().Width(p.colWidth).Height(p.colHeight).Render(p.areas[p.area].View())
} else {
area = blurredStyle.Copy().Width(p.colWidth).Height(p.colHeight).Render(p.areas[p.area].View())
} }
if p.task.Uuid != "" { if p.task.Uuid != "" {
var infoView string
if p.columnCursor == 2 {
infoView = focusedStyle.Copy().Width(p.infoViewport.Width).Height(p.infoViewport.Height).Render(p.infoViewport.View())
} else {
infoView = blurredStyle.Copy().Width(p.infoViewport.Width).Height(p.infoViewport.Height).Render(p.infoViewport.View())
}
area = lipgloss.JoinHorizontal( area = lipgloss.JoinHorizontal(
lipgloss.Top, lipgloss.Top,
area, area,
p.common.Styles.ColumnFocused.Render(p.common.TW.GetInformation(&p.task)), infoView,
) )
} }
tabs := "" tabs := ""
@ -305,8 +349,11 @@ type area interface {
tea.Model tea.Model
SetCursor(c int) SetCursor(c int)
GetName() string GetName() string
IsFiltering() bool
} }
type focusMsg struct{}
type areaPicker struct { type areaPicker struct {
common *common.Common common *common.Common
list list.Model list list.Model
@ -378,26 +425,54 @@ func (a *areaPicker) View() string {
return a.list.View() return a.list.View()
} }
type EditableField interface {
tea.Model
Focus() tea.Cmd
Blur() tea.Cmd
}
type taskEdit struct { type taskEdit struct {
common *common.Common common *common.Common
fields []huh.Field fields []EditableField
cursor int cursor int
projectPicker *picker.Picker
// newProjectName *string // newProjectName *string
newAnnotation *string newAnnotation *string
udaValues map[string]*string udaValues map[string]*string
} }
func NewTaskEdit(com *common.Common, task *taskwarrior.Task) *taskEdit { func NewTaskEdit(com *common.Common, task *taskwarrior.Task, isNew bool) *taskEdit {
// newProject := "" // newProject := ""
projectOptions := append([]string{"(none)"}, com.TW.GetProjects()...)
if task.Project == "" { if task.Project == "" {
task.Project = "(none)" task.Project = "(none)"
} }
itemProvider := func() []list.Item {
projects := com.TW.GetProjects()
items := []list.Item{picker.NewItem("(none)")}
for _, proj := range projects {
items = append(items, picker.NewItem(proj))
}
return items
}
onSelect := func(item list.Item) tea.Cmd {
return nil
}
opts := []picker.PickerOption{}
if isNew {
opts = append(opts, picker.WithFilterByDefault(true))
}
projPicker := picker.New(com, "Project", itemProvider, onSelect, opts...)
projPicker.SetSize(70, 8)
projPicker.SelectItemByFilterValue(task.Project)
projPicker.Blur()
defaultKeymap := huh.NewDefaultKeyMap() defaultKeymap := huh.NewDefaultKeyMap()
fields := []huh.Field{ fields := []EditableField{
huh.NewInput(). huh.NewInput().
Title("Task"). Title("Task").
Value(&task.Description). Value(&task.Description).
@ -411,12 +486,7 @@ func NewTaskEdit(com *common.Common, task *taskwarrior.Task) *taskEdit {
Prompt(": "). Prompt(": ").
WithTheme(com.Styles.Form), WithTheme(com.Styles.Form),
input.NewSelect(com). projPicker,
Options(true, input.NewOptions(projectOptions...)...).
Title("Project").
Value(&task.Project).
WithKeyMap(defaultKeymap).
WithTheme(com.Styles.Form),
// huh.NewInput(). // huh.NewInput().
// Title("New Project"). // Title("New Project").
@ -509,8 +579,9 @@ func NewTaskEdit(com *common.Common, task *taskwarrior.Task) *taskEdit {
WithTheme(com.Styles.Form)) WithTheme(com.Styles.Form))
t := taskEdit{ t := taskEdit{
common: com, common: com,
fields: fields, fields: fields,
projectPicker: projPicker,
udaValues: udaValues, udaValues: udaValues,
@ -527,6 +598,13 @@ func (t *taskEdit) GetName() string {
return "Task" return "Task"
} }
func (t *taskEdit) IsFiltering() bool {
if f, ok := t.fields[t.cursor].(interface{ IsFiltering() bool }); ok {
return f.IsFiltering()
}
return false
}
func (t *taskEdit) SetCursor(c int) { func (t *taskEdit) SetCursor(c int) {
t.fields[t.cursor].Blur() t.fields[t.cursor].Blur()
if c < 0 { if c < 0 {
@ -538,11 +616,25 @@ func (t *taskEdit) SetCursor(c int) {
} }
func (t *taskEdit) Init() tea.Cmd { func (t *taskEdit) Init() tea.Cmd {
return nil var cmds []tea.Cmd
// Ensure focus on the active field (especially for the first one)
if len(t.fields) > 0 {
cmds = append(cmds, func() tea.Msg {
return focusMsg{}
})
}
for _, f := range t.fields {
cmds = append(cmds, f.Init())
}
return tea.Batch(cmds...)
} }
func (t *taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (t *taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.(type) { switch msg.(type) {
case focusMsg:
if len(t.fields) > 0 {
return t, t.fields[t.cursor].Focus()
}
case nextFieldMsg: case nextFieldMsg:
if t.cursor == len(t.fields)-1 { if t.cursor == len(t.fields)-1 {
t.fields[t.cursor].Blur() t.fields[t.cursor].Blur()
@ -561,7 +653,7 @@ func (t *taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
t.fields[t.cursor].Focus() t.fields[t.cursor].Focus()
default: default:
field, cmd := t.fields[t.cursor].Update(msg) field, cmd := t.fields[t.cursor].Update(msg)
t.fields[t.cursor] = field.(huh.Field) t.fields[t.cursor] = field.(EditableField)
return t, cmd return t, cmd
} }
@ -624,6 +716,13 @@ func (t *tagEdit) GetName() string {
return "Tags" return "Tags"
} }
func (t *tagEdit) IsFiltering() bool {
if f, ok := t.fields[t.cursor].(interface{ IsFiltering() bool }); ok {
return f.IsFiltering()
}
return false
}
func (t *tagEdit) SetCursor(c int) { func (t *tagEdit) SetCursor(c int) {
t.fields[t.cursor].Blur() t.fields[t.cursor].Blur()
if c < 0 { if c < 0 {
@ -736,6 +835,10 @@ func (t *timeEdit) GetName() string {
return "Dates" return "Dates"
} }
func (t *timeEdit) IsFiltering() bool {
return false
}
func (t *timeEdit) SetCursor(c int) { func (t *timeEdit) SetCursor(c int) {
if len(t.fields) == 0 { if len(t.fields) == 0 {
return return
@ -868,6 +971,10 @@ func (d *detailsEdit) GetName() string {
return "Details" return "Details"
} }
func (d *detailsEdit) IsFiltering() bool {
return false
}
func (d *detailsEdit) SetCursor(c int) { func (d *detailsEdit) SetCursor(c int) {
} }
@ -1035,6 +1142,8 @@ func (d *detailsEdit) View() string {
// } // }
func (p *TaskEditorPage) updateTasksCmd() tea.Msg { func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
p.task.Project = p.areas[0].(*taskEdit).projectPicker.GetValue()
if p.task.Project == "(none)" { if p.task.Project == "(none)" {
p.task.Project = "" p.task.Project = ""
} }