Files
tasksquire/internal/components/taskEditor.go
Martin Pander 6f77b03555 WIP
2026-05-09 23:41:46 +02:00

1044 lines
26 KiB
Go

// package components
//
// import (
// "fmt"
// "log/slog"
// "tasksquire/common"
// "time"
//
// "tasksquire/components/input"
// "tasksquire/components/picker"
// "tasksquire/components/timestampeditor"
// "tasksquire/taskwarrior"
//
// "github.com/charmbracelet/bubbles/key"
// "github.com/charmbracelet/bubbles/list"
// "github.com/charmbracelet/bubbles/textarea"
// "github.com/charmbracelet/bubbles/viewport"
// tea "github.com/charmbracelet/bubbletea"
// "github.com/charmbracelet/huh"
// "github.com/charmbracelet/lipgloss"
// )
//
// type mode int
//
// const (
// modeNormal mode = iota
// modeInsert
// )
//
// type TaskEditorPage struct {
// common *common.Common
// task taskwarrior.Task
//
// colWidth int
// colHeight int
//
// mode mode
//
// columnCursor int
//
// area int
// areaPicker *areaPicker
// areas []area
//
// infoViewport viewport.Model
// }
//
// func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPage {
// p := TaskEditorPage{
// common: com,
// task: task,
// }
//
// if p.task.Project == "" {
// p.task.Project = "(none)"
// }
//
// tagOptions := p.common.TW.GetTags()
//
// p.areas = []area{
// NewTaskEdit(p.common, &p.task, p.task.Uuid == ""),
// 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)
// // p.selectedArea = areaTask
// // p.columns = append([]tea.Model{p.areaList}, p.areas[p.selectedArea]...)
//
// 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
// if p.task.Uuid == "" {
// 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()
// }
//
// 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 {
// 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) {
// switch msg := msg.(type) {
// case tea.WindowSizeMsg:
// p.SetSize(msg.Width, msg.Height)
// case changeAreaMsg:
// p.area = int(msg)
// case changeModeMsg:
// p.mode = mode(msg)
// case prevColumnMsg:
// p.columnCursor--
// maxCols := 2
// if p.task.Uuid != "" {
// maxCols = 3
// }
// if p.columnCursor < 0 {
// p.columnCursor = maxCols - 1
// }
// case nextColumnMsg:
// p.columnCursor++
// maxCols := 2
// if p.task.Uuid != "" {
// maxCols = 3
// }
// if p.columnCursor >= maxCols {
// p.columnCursor = 0
// }
// case prevAreaMsg:
// p.area--
// if p.area < 0 {
// p.area = len(p.areas) - 1
// }
// p.areas[p.area].SetCursor(-1)
// case nextAreaMsg:
// p.area++
// if p.area > len(p.areas)-1 {
// p.area = 0
// }
// p.areas[p.area].SetCursor(0)
// }
//
// switch p.mode {
// case modeNormal:
// switch msg := msg.(type) {
// case tea.KeyMsg:
// switch {
// case key.Matches(msg, p.common.Keymap.Back):
// model, err := p.common.PopPage()
// if err != nil {
// slog.Error("page stack empty")
// return nil, tea.Quit
// }
// return model, BackCmd
// case key.Matches(msg, p.common.Keymap.Insert):
// return p, changeMode(modeInsert)
// case key.Matches(msg, p.common.Keymap.Ok):
// model, err := p.common.PopPage()
// if err != nil {
// slog.Error("page stack empty")
// return nil, tea.Quit
// }
// return model, p.updateTasksCmd
// case key.Matches(msg, p.common.Keymap.PrevPage):
// return p, prevArea()
// case key.Matches(msg, p.common.Keymap.NextPage):
// return p, nextArea()
// case key.Matches(msg, p.common.Keymap.Left):
// return p, prevColumn()
// case key.Matches(msg, p.common.Keymap.Right):
// return p, nextColumn()
// case key.Matches(msg, p.common.Keymap.Up):
// if p.columnCursor == 0 {
// picker, cmd := p.areaPicker.Update(msg)
// p.areaPicker = picker.(*areaPicker)
// return p, cmd
// } else if p.columnCursor == 1 {
// model, cmd := p.areas[p.area].Update(prevFieldMsg{})
// p.areas[p.area] = model.(area)
// return p, cmd
// } else if p.columnCursor == 2 {
// p.infoViewport.LineUp(1)
// return p, nil
// }
// case key.Matches(msg, p.common.Keymap.Down):
// if p.columnCursor == 0 {
// picker, cmd := p.areaPicker.Update(msg)
// p.areaPicker = picker.(*areaPicker)
// return p, cmd
// } else if p.columnCursor == 1 {
// model, cmd := p.areas[p.area].Update(nextFieldMsg{})
// p.areas[p.area] = model.(area)
// return p, cmd
// } else if p.columnCursor == 2 {
// p.infoViewport.LineDown(1)
// return p, nil
// }
// }
// }
//
// // var cmd tea.Cmd
// // if p.columnCursor == 0 {
// // p., cmd = p.areaList.Update(msg)
// // p.selectedArea = p.areaList.(areaList).Area()
// // cmds = append(cmds, cmd)
// // } else {
// // p.areas[p.selectedArea][p.columnCursor-1], cmd = p.areas[p.selectedArea][p.columnCursor-1].Update(msg)
// // cmds = append(cmds, cmd)
// // }
// case modeInsert:
// switch msg := msg.(type) {
// case tea.KeyMsg:
// switch {
// case key.Matches(msg, p.common.Keymap.Back):
// model, cmd := p.areas[p.area].Update(msg)
// p.areas[p.area] = model.(area)
// if cmd != nil {
// _, ok := cmd().(input.SuppressBackMsg)
// if ok {
// return p, nil
// }
// }
// return p, changeMode(modeNormal)
// case key.Matches(msg, p.common.Keymap.Prev):
// if p.columnCursor == 0 {
// picker, cmd := p.areaPicker.Update(msg)
// p.areaPicker = picker.(*areaPicker)
// return p, cmd
// } else if p.columnCursor == 1 {
// model, cmd := p.areas[p.area].Update(prevFieldMsg{})
// p.areas[p.area] = model.(area)
// return p, cmd
// }
// return p, nil
// case key.Matches(msg, p.common.Keymap.Next):
// if p.columnCursor == 0 {
// picker, cmd := p.areaPicker.Update(msg)
// p.areaPicker = picker.(*areaPicker)
// return p, cmd
// } else if p.columnCursor == 1 {
// model, cmd := p.areas[p.area].Update(nextFieldMsg{})
// p.areas[p.area] = model.(area)
// return p, cmd
// }
// return p, nil
// case key.Matches(msg, p.common.Keymap.Ok):
// isFiltering := p.areas[p.area].IsFiltering()
// model, cmd := p.areas[p.area].Update(msg)
// if p.area != 3 {
// p.areas[p.area] = model.(area)
// if isFiltering {
// return p, cmd
// }
// return p, tea.Batch(cmd, nextField())
// }
// return p, cmd
// }
// }
//
// if p.columnCursor == 0 {
// picker, cmd := p.areaPicker.Update(msg)
// p.areaPicker = picker.(*areaPicker)
// return p, cmd
// } else if p.columnCursor == 2 {
// var cmd tea.Cmd
// p.infoViewport, cmd = p.infoViewport.Update(msg)
// return p, cmd
// } else {
// model, cmd := p.areas[p.area].Update(msg)
// p.areas[p.area] = model.(area)
// return p, cmd
// }
// }
// return p, nil
// }
//
// func (p *TaskEditorPage) View() string {
// var focusedStyle, blurredStyle lipgloss.Style
// if p.mode == modeInsert {
// focusedStyle = p.common.Styles.ColumnInsert
// } else {
// focusedStyle = p.common.Styles.ColumnFocused
// }
// blurredStyle = p.common.Styles.ColumnBlurred
//
// 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 != "" {
// 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(
// lipgloss.Top,
// area,
// infoView,
// )
// }
//
// tabs := ""
// for i, a := range p.areas {
// style := p.common.Styles.Base
// if i == p.area {
// style = style.Bold(true).Foreground(p.common.Styles.Palette.Accent.GetForeground())
// } else {
// style = style.Foreground(p.common.Styles.Palette.Muted.GetForeground())
// }
// tabs += style.Render(fmt.Sprintf(" %s ", a.GetName()))
// }
//
// page := lipgloss.JoinVertical(
// lipgloss.Left,
// tabs,
// area,
// )
//
// // 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, page)
// }
//
// type area interface {
// tea.Model
// SetCursor(c int)
// GetName() string
// IsFiltering() bool
// }
//
// type focusMsg struct{}
//
// type areaPicker struct {
// common *common.Common
// list list.Model
// }
//
// type item string
//
// func (i item) Title() string { return string(i) }
// func (i item) Description() string { return "test" }
// func (i item) FilterValue() string { return "" }
//
// func NewAreaPicker(common *common.Common, items []string) *areaPicker {
// listItems := make([]list.Item, len(items))
// for i, itm := range items {
// listItems[i] = item(itm)
// }
//
// list := list.New(listItems, list.DefaultDelegate{}, 20, 50)
// list.SetFilteringEnabled(false)
// list.SetShowStatusBar(false)
// list.SetShowHelp(false)
// list.SetShowPagination(false)
// list.SetShowTitle(false)
//
// return &areaPicker{
// common: common,
// list: list,
// }
// }
//
// 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 {
// return nil
// }
//
// func (a *areaPicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// var cmds []tea.Cmd
// var cmd tea.Cmd
// cursor := a.list.Cursor()
// // switch msg.(type) {
// // case nextFieldMsg:
// // a.list, cmd = a.list.Update(a.list.KeyMap.CursorDown)
// // case prevFieldMsg:
// // a.list, cmd = a.list.Update(a.list.KeyMap.CursorUp)
// // }
// a.list, cmd = a.list.Update(msg)
// cmds = append(cmds, cmd)
// if cursor != a.list.Cursor() {
// cmds = append(cmds, changeArea(a.Area()))
// }
//
// return a, tea.Batch(cmds...)
// }
//
// func (a *areaPicker) View() string {
// return a.list.View()
// }
//
// type EditableField interface {
// tea.Model
// Focus() tea.Cmd
// Blur() tea.Cmd
// }
//
// type taskEdit struct {
// common *common.Common
// fields []EditableField
// cursor int
//
// projectPicker *picker.Picker
// // newProjectName *string
// newAnnotation *string
// udaValues map[string]*string
// }
//
// func NewTaskEdit(com *common.Common, task *taskwarrior.Task, isNew bool) *taskEdit {
// // newProject := ""
// if task.Project == "" {
// 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
// }
// onCreate := func(newProject string) tea.Cmd {
// // The new project name will be used as the project value
// // when the task is saved
// return nil
// }
//
// opts := []picker.PickerOption{picker.WithOnCreate(onCreate)}
//
// // Check if task has a pre-filled project (e.g., from ProjectTaskPickerPage)
// hasPrefilledProject := task.Project != "" && task.Project != "(none)"
//
// if isNew && !hasPrefilledProject {
// // New task with no project → start in filter mode for quick project search
// opts = append(opts, picker.WithFilterByDefault(true))
// } else {
// // Either existing task OR new task with pre-filled project → show list with project selected
// opts = append(opts, picker.WithDefaultValue(task.Project))
// }
//
// projPicker := picker.New(com, "Project", itemProvider, onSelect, opts...)
// projPicker.SetSize(70, 8)
// projPicker.Blur()
//
// defaultKeymap := huh.NewDefaultKeyMap()
//
// fields := []EditableField{
// 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),
//
// projPicker,
//
// // huh.NewInput().
// // Title("New Project").
// // Value(&newProject).
// // Inline(true).
// // Prompt(": ").
// // WithTheme(com.Styles.Form),
// }
//
// udaValues := make(map[string]*string)
// for _, uda := range com.Udas {
// if uda.Name == "parenttask" {
// continue
// }
// 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).
// 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
//
// 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))
// }
// }
//
// newAnnotation := ""
// fields = append(fields, huh.NewInput().
// Title("New Annotation").
// Value(&newAnnotation).
// Inline(true).
// Prompt(": ").
// WithTheme(com.Styles.Form))
//
// t := taskEdit{
// common: com,
// fields: fields,
// projectPicker: projPicker,
//
// udaValues: udaValues,
//
// // newProjectName: &newProject,
// newAnnotation: &newAnnotation,
// }
//
// t.fields[0].Focus()
//
// return &t
// }
//
// func (t *taskEdit) GetName() string {
// 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) {
// 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 {
// 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) {
// switch msg.(type) {
// case focusMsg:
// if len(t.fields) > 0 {
// return t, t.fields[t.cursor].Focus()
// }
// case nextFieldMsg:
// if t.cursor == len(t.fields)-1 {
// t.fields[t.cursor].Blur()
// return t, nextArea()
// }
// t.fields[t.cursor].Blur()
// t.cursor++
// t.fields[t.cursor].Focus()
// case prevFieldMsg:
// if t.cursor == 0 {
// t.fields[t.cursor].Blur()
// return t, prevArea()
// }
// t.fields[t.cursor].Blur()
// t.cursor--
// t.fields[t.cursor].Focus()
// default:
// field, cmd := t.fields[t.cursor].Update(msg)
// t.fields[t.cursor] = field.(EditableField)
// return t, cmd
// }
//
// return t, nil
// }
//
// 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,
// views...,
// )
// }
//
// type tagEdit struct {
// common *common.Common
// fields []huh.Field
//
// cursor int
// }
//
// func NewTagEdit(common *common.Common, selected *[]string, options []string) *tagEdit {
// defaultKeymap := huh.NewDefaultKeyMap()
//
// t := tagEdit{
// common: common,
// fields: []huh.Field{
// input.NewMultiSelect(common).
// Options(true, input.NewOptions(options...)...).
// // Key("tags").
// Title("Tags").
// Value(selected).
// Filterable(true).
// WithKeyMap(defaultKeymap).
// WithTheme(common.Styles.Form),
// },
// }
//
// return &t
// }
//
// func (t *tagEdit) GetName() string {
// 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) {
// 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
// }
//
// func (t *tagEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// switch msg.(type) {
// case nextFieldMsg:
// if t.cursor == len(t.fields)-1 {
// t.fields[t.cursor].Blur()
// return t, nextArea()
// }
// t.fields[t.cursor].Blur()
// t.cursor++
// t.fields[t.cursor].Focus()
// case prevFieldMsg:
// if t.cursor == 0 {
// t.fields[t.cursor].Blur()
// return t, prevArea()
// }
// t.fields[t.cursor].Blur()
// t.cursor--
// t.fields[t.cursor].Focus()
// default:
// field, cmd := t.fields[t.cursor].Update(msg)
// t.fields[t.cursor] = field.(huh.Field)
// return t, cmd
// }
// return t, nil
// }
//
// func (t tagEdit) View() string {
// views := make([]string, len(t.fields))
// for i, field := range t.fields {
// views[i] = field.View()
// }
// return lipgloss.JoinVertical(
// lipgloss.Left,
// views...,
// )
// }
//
// type timeEdit struct {
// common *common.Common
// fields []*timestampeditor.TimestampEditor
//
// cursor int
//
// // Store task field pointers to update them
// due *string
// scheduled *string
// wait *string
// until *string
// }
//
// func NewTimeEdit(common *common.Common, due *string, scheduled *string, wait *string, until *string) *timeEdit {
// // Create timestamp editors for each date field
// dueEditor := timestampeditor.New(common).
// Title("Due").
// Description("When the task is due").
// ValueFromString(*due)
//
// scheduledEditor := timestampeditor.New(common).
// Title("Scheduled").
// Description("When to start working on the task").
// ValueFromString(*scheduled)
//
// waitEditor := timestampeditor.New(common).
// Title("Wait").
// Description("Hide task until this date").
// ValueFromString(*wait)
//
// untilEditor := timestampeditor.New(common).
// Title("Until").
// Description("Task expires after this date").
// ValueFromString(*until)
//
// t := timeEdit{
// common: common,
// fields: []*timestampeditor.TimestampEditor{
// dueEditor,
// scheduledEditor,
// waitEditor,
// untilEditor,
// },
// due: due,
// scheduled: scheduled,
// wait: wait,
// until: until,
// }
//
// // Focus the first field
// if len(t.fields) > 0 {
// t.fields[0].Focus()
// }
//
// return &t
// }
//
// func (t *timeEdit) GetName() string {
// return "Dates"
// }
//
// func (t *timeEdit) IsFiltering() bool {
// return false
// }
//
// func (t *timeEdit) SetCursor(c int) {
// if len(t.fields) == 0 {
// return
// }
//
// // Blur the current field
// t.fields[t.cursor].Blur()
//
// // Set new cursor position
// if c < 0 {
// t.cursor = len(t.fields) - 1
// } else {
// t.cursor = c
// }
//
// // Focus the new field
// t.fields[t.cursor].Focus()
// }
//
// func (t *timeEdit) Init() tea.Cmd {
// return nil
// }
//
// func (t *timeEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// switch msg := msg.(type) {
// case nextFieldMsg:
// if t.cursor == len(t.fields)-1 {
// // Update task field before moving to next area
// t.syncToTaskFields()
// t.fields[t.cursor].Blur()
// return t, nextArea()
// }
// t.fields[t.cursor].Blur()
// t.cursor++
// t.fields[t.cursor].Focus()
// return t, nil
// case prevFieldMsg:
// if t.cursor == 0 {
// // Update task field before moving to previous area
// t.syncToTaskFields()
// t.fields[t.cursor].Blur()
// return t, prevArea()
// }
// t.fields[t.cursor].Blur()
// t.cursor--
// t.fields[t.cursor].Focus()
// return t, nil
// default:
// // Update the current timestamp editor
// model, cmd := t.fields[t.cursor].Update(msg)
// t.fields[t.cursor] = model.(*timestampeditor.TimestampEditor)
// return t, cmd
// }
// }
//
// func (t *timeEdit) 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,
// views...,
// )
// }
//
// // syncToTaskFields converts the timestamp editor values back to task field strings
// func (t *timeEdit) syncToTaskFields() {
// // Update the task fields with values from the timestamp editors
// // GetValueString() returns empty string for unset timestamps
// if len(t.fields) > 0 {
// *t.due = t.fields[0].GetValueString()
// }
// if len(t.fields) > 1 {
// *t.scheduled = t.fields[1].GetValueString()
// }
// if len(t.fields) > 2 {
// *t.wait = t.fields[2].GetValueString()
// }
// if len(t.fields) > 3 {
// *t.until = t.fields[3].GetValueString()
// }
// }
//
// type detailsEdit struct {
// com *common.Common
// vp viewport.Model
// ta textarea.Model
// details string
// // renderer *glamour.TermRenderer
// }
//
// 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(com.Width(), 40-com.Styles.ColumnFocused.GetVerticalFrameSize())
// ta := textarea.New()
// ta.SetWidth(70)
// ta.SetHeight(40 - com.Styles.ColumnFocused.GetVerticalFrameSize() - 2)
// ta.ShowLineNumbers = false
// ta.FocusedStyle.CursorLine = lipgloss.NewStyle()
// ta.Focus()
// if task.Udas["details"] != nil {
// ta.SetValue(task.Udas["details"].(string))
// }
// d := detailsEdit{
// com: com,
// // renderer: renderer,
// vp: vp,
// ta: ta,
// }
//
// return &d
// }
//
// func (d *detailsEdit) GetName() string {
// return "Details"
// }
//
// func (d *detailsEdit) IsFiltering() bool {
// return false
// }
//
// func (d *detailsEdit) SetCursor(c int) {
// }
//
// func (d *detailsEdit) Init() tea.Cmd {
// return textarea.Blink
// }
//
// 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 vpCmd, taCmd tea.Cmd
// d.vp, vpCmd = d.vp.Update(msg)
// d.ta, taCmd = d.ta.Update(msg)
// return d, tea.Batch(vpCmd, taCmd)
// }
// }
//
// func (d *detailsEdit) View() string {
// return d.ta.View()
// }
//
// func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
// p.task.Project = p.areas[0].(*taskEdit).projectPicker.GetValue()
//
// if p.task.Project == "(none)" {
// p.task.Project = ""
// }
//
// 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
// }
// }
//
// // Sync timestamp fields from the timeEdit area (area 2)
// p.areas[2].(*timeEdit).syncToTaskFields()
//
// 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),
// })
// }
//
// if _, ok := p.task.Udas["details"]; ok || p.areas[3].(*detailsEdit).ta.Value() != "" {
// p.task.Udas["details"] = p.areas[3].(*detailsEdit).ta.Value()
// }
//
// p.common.TW.ImportTask(&p.task)
// return UpdatedTasksMsg{}
// }
//
// // type StatusLine struct { ... }
// // ...