v1!!! a lot of fixes and new addTODO feature!
This commit is contained in:
parent
43843c705d
commit
9946c4ffce
531
caldav.go
531
caldav.go
@ -3,15 +3,15 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
// "fmt"
|
// "fmt"
|
||||||
|
"github.com/emersion/go-ical"
|
||||||
webdav "github.com/emersion/go-webdav"
|
webdav "github.com/emersion/go-webdav"
|
||||||
"github.com/emersion/go-webdav/caldav"
|
"github.com/emersion/go-webdav/caldav"
|
||||||
"github.com/emersion/go-ical"
|
|
||||||
"github.com/teambition/rrule-go"
|
"github.com/teambition/rrule-go"
|
||||||
|
|
||||||
"strings"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"time"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
// "net/url"
|
// "net/url"
|
||||||
@ -30,7 +30,7 @@ import (
|
|||||||
// DueDateTime time.Time //for adding new events
|
// DueDateTime time.Time //for adding new events
|
||||||
// }
|
// }
|
||||||
|
|
||||||
//TODO Is it safe to create global variables?
|
// TODO Is it safe to create global variables?
|
||||||
var clientWebDAV *webdav.Client
|
var clientWebDAV *webdav.Client
|
||||||
var client *caldav.Client // clientCalDAV
|
var client *caldav.Client // clientCalDAV
|
||||||
// var calendarObjects []caldav.CalendarObject
|
// var calendarObjects []caldav.CalendarObject
|
||||||
@ -38,10 +38,10 @@ var ctx = context.Background()
|
|||||||
|
|
||||||
// var authSession caldav.Client // clientCalDAV
|
// var authSession caldav.Client // clientCalDAV
|
||||||
|
|
||||||
func InitDAVclients(url,user,pass string) error {
|
func InitDAVclients(url, user, pass string) error {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
authSession := webdav.HTTPClientWithBasicAuth(nil, user,pass)
|
authSession := webdav.HTTPClientWithBasicAuth(nil, user, pass)
|
||||||
|
|
||||||
clientWebDAV, err = webdav.NewClient(authSession, url)
|
clientWebDAV, err = webdav.NewClient(authSession, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -58,7 +58,7 @@ func InitDAVclients(url,user,pass string) error {
|
|||||||
|
|
||||||
func (options *Options) InitDAVclients() error {
|
func (options *Options) InitDAVclients() error {
|
||||||
|
|
||||||
err := InitDAVclients(options.URL,options.User, options.Password)
|
err := InitDAVclients(options.URL, options.User, options.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Handle error
|
// Handle error
|
||||||
return err
|
return err
|
||||||
@ -100,7 +100,7 @@ func GetCalendars() ([]caldav.Calendar, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err error) {
|
func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err error) {
|
||||||
//TODO can we use calendar.Data.Component.Children to dont make this close to pointless request?
|
//TODO can we use calendar.Data.Component.Children to dont make this close to pointless request?
|
||||||
date := time.Now()
|
date := time.Now()
|
||||||
dateStart := time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 0, date.Location()).AddDate(0, 0, -1) //TODO too complex - time.Now().Add(-92 * time.Hour),
|
dateStart := time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 0, date.Location()).AddDate(0, 0, -1) //TODO too complex - time.Now().Add(-92 * time.Hour),
|
||||||
// dateEnd:= time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 0, date.Location()) //date +1 day
|
// dateEnd:= time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 0, date.Location()) //date +1 day
|
||||||
@ -144,7 +144,7 @@ func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err
|
|||||||
|
|
||||||
// func ParseDueDateTODOs_depricated(calObjs []caldav.CalendarObject, date time.Time) ([]TODO, error) {
|
// func ParseDueDateTODOs_depricated(calObjs []caldav.CalendarObject, date time.Time) ([]TODO, error) {
|
||||||
// var output []TODO
|
// var output []TODO
|
||||||
//
|
//
|
||||||
// for _, calObj := range calObjs {
|
// for _, calObj := range calObjs {
|
||||||
// // fmt.Println((*(*calObj.Data).Children[0]).Name)
|
// // fmt.Println((*(*calObj.Data).Children[0]).Name)
|
||||||
// // TODO STATUS map[] COMPLETED
|
// // TODO STATUS map[] COMPLETED
|
||||||
@ -153,7 +153,7 @@ func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err
|
|||||||
// // var notCompletedTODO, withDate, fromToday bool
|
// // var notCompletedTODO, withDate, fromToday bool
|
||||||
// var notCompletedTODO, fromToday bool
|
// var notCompletedTODO, fromToday bool
|
||||||
// //TODO we can optimize there if we encounter wrong state to forcefully stop next analysis
|
// //TODO we can optimize there if we encounter wrong state to forcefully stop next analysis
|
||||||
// //TODO we can use event.Props.Get
|
// //TODO we can use event.Props.Get
|
||||||
// // notCompletedTODO
|
// // notCompletedTODO
|
||||||
// if (*event).Props["COMPLETED"] == nil {
|
// if (*event).Props["COMPLETED"] == nil {
|
||||||
// if (*event).Props["STATUS"] == nil {
|
// if (*event).Props["STATUS"] == nil {
|
||||||
@ -164,7 +164,7 @@ func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// // withTodayDate
|
// // withTodayDate
|
||||||
// if (*event).Props["DUE"] != nil {
|
// if (*event).Props["DUE"] != nil {
|
||||||
// // withDate = true
|
// // withDate = true
|
||||||
@ -173,12 +173,12 @@ func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err
|
|||||||
// fromToday = true
|
// fromToday = true
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// // if notCompletedTODO && withDate && fromToday {
|
// // if notCompletedTODO && withDate && fromToday {
|
||||||
// if notCompletedTODO && fromToday {
|
// if notCompletedTODO && fromToday {
|
||||||
//
|
//
|
||||||
// var tmpTODO TODO
|
// var tmpTODO TODO
|
||||||
//
|
//
|
||||||
// if (*event).Props["SUMMARY"] != nil {
|
// if (*event).Props["SUMMARY"] != nil {
|
||||||
// name := (*event).Props["SUMMARY"][0].Value
|
// name := (*event).Props["SUMMARY"][0].Value
|
||||||
// tmpTODO.Name = name
|
// tmpTODO.Name = name
|
||||||
@ -189,14 +189,14 @@ func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err
|
|||||||
// description := (*event).Props["DESCRIPTION"][0].Value
|
// description := (*event).Props["DESCRIPTION"][0].Value
|
||||||
// tmpTODO.Desc = description
|
// tmpTODO.Desc = description
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// var todoTime string
|
// var todoTime string
|
||||||
// due := (*event).Props["DUE"][0].Value
|
// due := (*event).Props["DUE"][0].Value
|
||||||
// index := strings.Index(due, "T")
|
// index := strings.Index(due, "T")
|
||||||
// if index != -1 {
|
// if index != -1 {
|
||||||
// str := due[index+1:]
|
// str := due[index+1:]
|
||||||
// todoTime = str[:2] + ":" + str[2:4]
|
// todoTime = str[:2] + ":" + str[2:4]
|
||||||
//
|
//
|
||||||
// tmpTODO.Time = todoTime
|
// tmpTODO.Time = todoTime
|
||||||
// }
|
// }
|
||||||
// output = append(output, tmpTODO)
|
// output = append(output, tmpTODO)
|
||||||
@ -204,7 +204,7 @@ func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err
|
|||||||
// // }
|
// // }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// // //TODO sort: time, priority (if no time)
|
// // //TODO sort: time, priority (if no time)
|
||||||
// // // it means, put DUE at first, other DUE;VALUE=DATE in priority order
|
// // // it means, put DUE at first, other DUE;VALUE=DATE in priority order
|
||||||
// // //TODO color: priority, add time icon if have time
|
// // //TODO color: priority, add time icon if have time
|
||||||
@ -212,15 +212,15 @@ func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err
|
|||||||
// // //TODO RELATED-TO:7ed30f40-fce1-422c-be3b-0486dcfe8943 # subtask
|
// // //TODO RELATED-TO:7ed30f40-fce1-422c-be3b-0486dcfe8943 # subtask
|
||||||
// // //TODO PRIORITY:1 #1-high, 5-mid, 9-low
|
// // //TODO PRIORITY:1 #1-high, 5-mid, 9-low
|
||||||
// //TODO repeat function???
|
// //TODO repeat function???
|
||||||
//
|
//
|
||||||
// //TODO on complete -repeat function
|
// //TODO on complete -repeat function
|
||||||
// // RRULE:FREQ=WEEKLY;INTERVAL=1
|
// // RRULE:FREQ=WEEKLY;INTERVAL=1
|
||||||
//
|
//
|
||||||
// //TODO if no repeat - mark as complted
|
// //TODO if no repeat - mark as complted
|
||||||
// // STATUS:COMPLETED
|
// // STATUS:COMPLETED
|
||||||
// // COMPLETED:20240421T065323Z
|
// // COMPLETED:20240421T065323Z
|
||||||
// // PERCENT-COMPLETE:100
|
// // PERCENT-COMPLETE:100
|
||||||
//
|
//
|
||||||
// //TODO support notifcations/alarms???
|
// //TODO support notifcations/alarms???
|
||||||
// // BEGIN:VTODO
|
// // BEGIN:VTODO
|
||||||
// // ...
|
// // ...
|
||||||
@ -230,40 +230,15 @@ func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err
|
|||||||
// // DESCRIPTION:Default Tasks.org description
|
// // DESCRIPTION:Default Tasks.org description
|
||||||
// // END:VALARM
|
// // END:VALARM
|
||||||
// // END:VTODO
|
// // END:VTODO
|
||||||
//
|
//
|
||||||
// return output, nil
|
// return output, nil
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.Event, error) {
|
func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.Event, error) {
|
||||||
var output []ical.Event
|
var output []ical.Event
|
||||||
|
|
||||||
for _, calObj := range calObjs {
|
for _, calObj := range calObjs {
|
||||||
|
|
||||||
// fmt.Println((*(*calObj.Data).Children[0]).Name)
|
// fmt.Println((*(*calObj.Data).Children[0]).Name)
|
||||||
// TODO STATUS map[] COMPLETED
|
// TODO STATUS map[] COMPLETED
|
||||||
// for _, event := range (*calObj.Data).Children {
|
// for _, event := range (*calObj.Data).Children {
|
||||||
@ -277,16 +252,16 @@ func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.
|
|||||||
// // fmt.Println("1:", event2)
|
// // fmt.Println("1:", event2)
|
||||||
// // }
|
// // }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
for _, eventComponent:= range (*calObj.Data).Children {
|
for _, eventComponent := range (*calObj.Data).Children {
|
||||||
event := ical.Event{Component: eventComponent}
|
event := ical.Event{Component: eventComponent}
|
||||||
// for _, event := range (*calObj.Data).Children {
|
// for _, event := range (*calObj.Data).Children {
|
||||||
// fmt.Println("1:", event) //TODO rm me
|
// fmt.Println("1:", event) //TODO rm me
|
||||||
// if (*event).Name == "VTODO" {
|
// if (*event).Name == "VTODO" {
|
||||||
// var notCompletedTODO, withDate, fromToday bool
|
// var notCompletedTODO, withDate, fromToday bool
|
||||||
var notCompletedTODO, fromToday bool
|
var notCompletedTODO, fromToday bool
|
||||||
//TODO we can optimize there if we encounter wrong state to forcefully stop next analysis
|
//TODO we can optimize there if we encounter wrong state to forcefully stop next analysis
|
||||||
//TODO we can use event.Props.Get
|
//TODO we can use event.Props.Get
|
||||||
// notCompletedTODO
|
// notCompletedTODO
|
||||||
if event.Props["COMPLETED"] == nil {
|
if event.Props["COMPLETED"] == nil {
|
||||||
if event.Props["STATUS"] == nil {
|
if event.Props["STATUS"] == nil {
|
||||||
@ -308,29 +283,30 @@ func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if notCompletedTODO && withDate && fromToday {
|
// if notCompletedTODO && withDate && fromToday {
|
||||||
if notCompletedTODO && fromToday {
|
if notCompletedTODO && fromToday {
|
||||||
|
|
||||||
tmpTODO := event
|
tmpTODO := event
|
||||||
|
|
||||||
if event.Props["SUMMARY"] == nil {
|
if event.Props["SUMMARY"] == nil {
|
||||||
tmpTODO.Props.SetText(ical.PropSummary, "<EMPTY>")
|
tmpTODO.Props.SetText(ical.PropSummary, "<EMPTY>")
|
||||||
}
|
}
|
||||||
if event.Props["DESCRIPTION"] == nil {
|
if event.Props["DESCRIPTION"] == nil {
|
||||||
tmpTODO.Props.SetText(ical.PropDescription, "")
|
tmpTODO.Props.SetText(ical.PropDescription, "")
|
||||||
}
|
}
|
||||||
|
if event.Props["PRIORITY"] == nil {
|
||||||
|
tmpTODO.Props.SetText("PRIORITY", "0")
|
||||||
|
}
|
||||||
|
|
||||||
// var todoTime string
|
// var todoTime string
|
||||||
// due := (*event).Props["DUE"][0].Value
|
// due := (*event).Props["DUE"][0].Value
|
||||||
// index := strings.Index(due, "T")
|
// index := strings.Index(due, "T")
|
||||||
// if index != -1 {
|
// if index != -1 {
|
||||||
// str := due[index+1:]
|
// str := due[index+1:]
|
||||||
// todoTime = str[:2] + ":" + str[2:4]
|
// todoTime = str[:2] + ":" + str[2:4]
|
||||||
//
|
//
|
||||||
// tmpTODO.Time = todoTime
|
// tmpTODO.Time = todoTime
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
output = append(output, tmpTODO)
|
output = append(output, tmpTODO)
|
||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
@ -343,15 +319,15 @@ func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.
|
|||||||
// //TODO UID:7ed30f40-fce1-422c-be3b-0486dcfe8943
|
// //TODO UID:7ed30f40-fce1-422c-be3b-0486dcfe8943
|
||||||
// //TODO RELATED-TO:7ed30f40-fce1-422c-be3b-0486dcfe8943 # subtask
|
// //TODO RELATED-TO:7ed30f40-fce1-422c-be3b-0486dcfe8943 # subtask
|
||||||
// //TODO PRIORITY:1 #1-high, 5-mid, 9-low
|
// //TODO PRIORITY:1 #1-high, 5-mid, 9-low
|
||||||
//TODO on complete -repeat function
|
//TODO on complete -repeat function
|
||||||
//TODO repeate - RRULE:FREQ=DAILY;INTERVAL=1
|
//TODO repeate - RRULE:FREQ=DAILY;INTERVAL=1
|
||||||
// RRULE:FREQ=WEEKLY;INTERVAL=1
|
// RRULE:FREQ=WEEKLY;INTERVAL=1
|
||||||
|
|
||||||
//TODO if no repeat - mark as complted
|
//TODO if no repeat - mark as complted
|
||||||
// STATUS:COMPLETED
|
// STATUS:COMPLETED
|
||||||
// COMPLETED:20240421T065323Z
|
// COMPLETED:20240421T065323Z
|
||||||
// PERCENT-COMPLETE:100
|
// PERCENT-COMPLETE:100
|
||||||
|
|
||||||
//TODO support notifcations/alarms???
|
//TODO support notifcations/alarms???
|
||||||
// BEGIN:VTODO
|
// BEGIN:VTODO
|
||||||
// ...
|
// ...
|
||||||
@ -367,145 +343,144 @@ func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type TodoInterface struct {
|
type TodoInterface struct {
|
||||||
name string
|
name string
|
||||||
description string
|
description string
|
||||||
priority int
|
priority int
|
||||||
dueTime time.Time
|
dueTime time.Time
|
||||||
alarmOffset string
|
alarmOffset string
|
||||||
repeat string
|
repeat string
|
||||||
//TODO repeat
|
//TODO repeat
|
||||||
//TODO subtasks
|
//TODO subtasks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateTodo(info TodoInterface) (event ical.Event, err error) {
|
||||||
func CreateTodo(info TodoInterface) (event ical.Event,err error) {
|
|
||||||
uid, err := uuid.NewUUID()
|
uid, err := uuid.NewUUID()
|
||||||
if err != nil {return}
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
event = *ical.NewEvent()
|
event = *ical.NewEvent()
|
||||||
event.Name = ical.CompToDo //VTODO
|
event.Name = ical.CompToDo //VTODO
|
||||||
event.Props.SetText(ical.PropUID, uid.String())
|
event.Props.SetText(ical.PropUID, uid.String())
|
||||||
event.Props.SetText(ical.PropSummary, info.name)
|
event.Props.SetText(ical.PropSummary, info.name)
|
||||||
event.Props.SetText(ical.PropDescription, info.description)
|
event.Props.SetText(ical.PropDescription, info.description)
|
||||||
if !info.dueTime.IsZero() {event.Props.SetDateTime(ical.PropDue, info.dueTime)} // 'zero' time is `time.Time{}`
|
if !info.dueTime.IsZero() {
|
||||||
//TODO add alarm
|
hour := info.dueTime.Hour()
|
||||||
switch info.priority {
|
minute := info.dueTime.Minute()
|
||||||
case 0: //No priority
|
if (hour != 0 || minute != 0 ) {
|
||||||
event.Props.SetText(ical.PropPriority, "0")
|
event.Props.SetDateTime(ical.PropDue, info.dueTime)
|
||||||
case 1: //Light
|
} else {
|
||||||
event.Props.SetText(ical.PropPriority, "9")
|
event.Props.SetDate(ical.PropDue, info.dueTime)
|
||||||
case 2: //Medium
|
|
||||||
event.Props.SetText(ical.PropPriority, "5")
|
|
||||||
case 3: //Urgent
|
|
||||||
event.Props.SetText(ical.PropPriority, "1")
|
|
||||||
default:
|
|
||||||
err = errors.New("Wrong priority, expecting 0-3")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
//TODO repeat - RRULE:FREQ=DAILY;INTERVAL=1
|
// }
|
||||||
if info.alarmOffset != "" {
|
} // 'zero' time is `time.Time{}`
|
||||||
|
|
||||||
// alarmComponent := ical.Component{Name:ical.CompAlarm}
|
switch info.priority {
|
||||||
alarmComponent := ical.NewComponent(ical.CompAlarm)
|
case 0: //No priority
|
||||||
|
event.Props.SetText(ical.PropPriority, "0")
|
||||||
|
case 1: //Light
|
||||||
|
event.Props.SetText(ical.PropPriority, "9")
|
||||||
|
case 2: //Medium
|
||||||
|
event.Props.SetText(ical.PropPriority, "5")
|
||||||
|
case 3: //Urgent
|
||||||
|
event.Props.SetText(ical.PropPriority, "1")
|
||||||
|
default:
|
||||||
|
err = errors.New("Wrong priority, expecting 0-3")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//TODO repeat - RRULE:FREQ=DAILY;INTERVAL=1
|
||||||
|
if info.alarmOffset != "" {
|
||||||
|
|
||||||
|
// alarmComponent := ical.Component{Name:ical.CompAlarm}
|
||||||
|
alarmComponent := ical.NewComponent(ical.CompAlarm)
|
||||||
|
|
||||||
|
alarmComponent.Props.SetText(ical.PropAction, "DISPLAY")
|
||||||
|
// alarmComponent.Props.Add(&ical.Prop{Name:ical.PropAction,Value:"DISPLAY",})
|
||||||
|
alarmComponent.Props.Add(&ical.Prop{Name: ical.PropDescription, Value: "Default Alarm Tempus description"})
|
||||||
|
alarmComponent.Props.SetText(ical.PropDescription, "Default Alarm Tempus description")
|
||||||
|
// ACTION:DISPLAY
|
||||||
|
// DESCRIPTION:Default Tasks.org description
|
||||||
|
var value string
|
||||||
|
if info.alarmOffset == "0" {
|
||||||
|
// TRIGGER;RELATED=END:PT0S
|
||||||
|
value = "PT0S"
|
||||||
|
|
||||||
alarmComponent.Props.SetText(ical.PropAction,"DISPLAY")
|
|
||||||
// alarmComponent.Props.Add(&ical.Prop{Name:ical.PropAction,Value:"DISPLAY",})
|
|
||||||
alarmComponent.Props.Add(&ical.Prop{Name:ical.PropDescription,Value:"Default Alarm Tempus description",})
|
|
||||||
alarmComponent.Props.SetText(ical.PropDescription,"Default Alarm Tempus description")
|
|
||||||
// ACTION:DISPLAY
|
|
||||||
// DESCRIPTION:Default Tasks.org description
|
|
||||||
var value string
|
|
||||||
if info.alarmOffset == "0" {
|
|
||||||
// TRIGGER;RELATED=END:PT0S
|
|
||||||
value = "PT0S"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasSuffix(info.alarmOffset,"h") {
|
|
||||||
offset,_ := strings.CutSuffix(info.alarmOffset,"h")
|
|
||||||
value="-PT" + offset + "H"
|
|
||||||
// TRIGGER;RELATED=END:-PT1H
|
|
||||||
|
|
||||||
}
|
|
||||||
//TODO stop next if
|
|
||||||
if strings.HasSuffix(info.alarmOffset,"m") {
|
|
||||||
offset,_ := strings.CutSuffix(info.alarmOffset,"m")
|
|
||||||
value="-PT" + offset + "M"
|
|
||||||
// TRIGGER;RELATED=END:-PT10M
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasSuffix(info.alarmOffset,"d") {
|
|
||||||
offset,_ := strings.CutSuffix(info.alarmOffset,"d")
|
|
||||||
value="-P" + offset + "D"
|
|
||||||
// TRIGGER;RELATED=END:-P1D
|
|
||||||
}
|
|
||||||
alarmComponent.Props.SetText("TRIGGER;RELATED=END",value)
|
|
||||||
event.Children = append(event.Children,alarmComponent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(info.alarmOffset, "h") {
|
||||||
event.Props.SetDateTime(ical.PropDateTimeStamp, time.Now().UTC())
|
offset, _ := strings.CutSuffix(info.alarmOffset, "h")
|
||||||
event.Props.SetDateTime(ical.PropCreated, time.Now().UTC()) //TODO if it don't exist already (in case if we edit todo)
|
value = "-PT" + offset + "H"
|
||||||
event.Props.SetDateTime(ical.PropLastModified, time.Now().UTC())
|
// TRIGGER;RELATED=END:-PT1H
|
||||||
//TODO add a function to verify event (exist in ical lib)
|
|
||||||
|
}
|
||||||
|
//TODO stop next if
|
||||||
|
if strings.HasSuffix(info.alarmOffset, "m") {
|
||||||
|
offset, _ := strings.CutSuffix(info.alarmOffset, "m")
|
||||||
|
value = "-PT" + offset + "M"
|
||||||
|
// TRIGGER;RELATED=END:-PT10M
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(info.alarmOffset, "d") {
|
||||||
|
offset, _ := strings.CutSuffix(info.alarmOffset, "d")
|
||||||
|
value = "-P" + offset + "D"
|
||||||
|
// TRIGGER;RELATED=END:-P1D
|
||||||
|
}
|
||||||
|
alarmComponent.Props.SetText("TRIGGER;RELATED=END", value)
|
||||||
|
event.Children = append(event.Children, alarmComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Props.SetDateTime(ical.PropDateTimeStamp, time.Now().UTC())
|
||||||
|
event.Props.SetDateTime(ical.PropCreated, time.Now().UTC()) //TODO if it don't exist already (in case if we edit todo)
|
||||||
|
event.Props.SetDateTime(ical.PropLastModified, time.Now().UTC())
|
||||||
|
//TODO add a function to verify event (exist in ical lib)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (m model) UploadTodo(event ical.Event) (err error) {
|
func (m model) UploadTodo(event ical.Event) (err error) {
|
||||||
// event.Props.SetDateTime(ical.PropDateTimeStart, startDateTime)
|
// event.Props.SetDateTime(ical.PropDateTimeStart, startDateTime)
|
||||||
|
|
||||||
// TODO Alarm component properties
|
// TODO Alarm component properties
|
||||||
// PropAction = "ACTION"
|
// PropAction = "ACTION"
|
||||||
// PropRepeat = "REPEAT"
|
// PropRepeat = "REPEAT"
|
||||||
// PropTrigger = "TRIGGER"}
|
// PropTrigger = "TRIGGER"}
|
||||||
|
|
||||||
// calendar, err := client.GetCalendarObject(ctx, m.Creds.CalendarPath) //makes error on nextcloud
|
// calendar, err := client.GetCalendarObject(ctx, m.Creds.CalendarPath) //makes error on nextcloud
|
||||||
// if err != nil {return err}
|
// if err != nil {return err}
|
||||||
|
|
||||||
calendar := ical.NewCalendar()
|
calendar := ical.NewCalendar()
|
||||||
calendar.Props.SetText(ical.PropProductID, "+//Casual//Tempus//EN")
|
calendar.Props.SetText(ical.PropProductID, "+//Casual//Tempus//EN")
|
||||||
calendar.Props.SetText(ical.PropVersion, "2.0")
|
calendar.Props.SetText(ical.PropVersion, "2.0")
|
||||||
calendar.Component.Children = append(calendar.Component.Children, event.Component)
|
calendar.Component.Children = append(calendar.Component.Children, event.Component)
|
||||||
|
|
||||||
todoGUID,err := event.Props.Get(ical.PropUID).Text()
|
todoGUID, err := event.Props.Get(ical.PropUID).Text()
|
||||||
if err != nil {return err}
|
if err != nil {
|
||||||
//TODO check GUID uniq and regenerate if needed
|
return err
|
||||||
|
}
|
||||||
|
//TODO check GUID uniq and regenerate if needed
|
||||||
|
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
encoder := ical.NewEncoder(&buf)
|
encoder := ical.NewEncoder(&buf)
|
||||||
err = encoder.Encode(calendar)
|
err = encoder.Encode(calendar)
|
||||||
if err != nil {return err}
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
_, err = client.PutCalendarObject(ctx, m.Creds.CalendarPath+todoGUID+".isc", calendar)
|
_, err = client.PutCalendarObject(ctx, m.Creds.CalendarPath+todoGUID+".isc", calendar)
|
||||||
if err != nil {return err}
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// func (m model) DelTodo(delUID string) (err error) {
|
// func (m model) DelTodo(delUID string) (err error) {
|
||||||
func (m model) DelTodo(todo ical.Event) (err error) {
|
func (m model) DelTodo(todo ical.Event) (err error) {
|
||||||
|
|
||||||
delUID,err := todo.Props.Get(ical.PropUID).Text()
|
delUID, err := todo.Props.Get(ical.PropUID).Text()
|
||||||
// if err != nil {return}
|
// if err != nil {return}
|
||||||
|
|
||||||
// calendar, err := client.GetCalendarObject(ctx, m.Creds.CalendarPath+delUID+".isc")
|
// calendar, err := client.GetCalendarObject(ctx, m.Creds.CalendarPath+delUID+".isc")
|
||||||
// if err != nil {return}
|
// if err != nil {return}
|
||||||
//
|
//
|
||||||
// var newEvents []*ical.Component
|
// var newEvents []*ical.Component
|
||||||
// for _, component := range calendar.Data.Component.Children {
|
// for _, component := range calendar.Data.Component.Children {
|
||||||
// if component.Name == ical.CompEvent {
|
// if component.Name == ical.CompEvent {
|
||||||
@ -515,155 +490,183 @@ func (m model) DelTodo(todo ical.Event) (err error) {
|
|||||||
// if uid != delUID {
|
// if uid != delUID {
|
||||||
// newEvents = append(newEvents, component)
|
// newEvents = append(newEvents, component)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// calendar.Data.Component.Children = newEvents
|
// calendar.Data.Component.Children = newEvents
|
||||||
// var buf strings.Builder
|
// var buf strings.Builder
|
||||||
// encoder := ical.NewEncoder(&buf)
|
// encoder := ical.NewEncoder(&buf)
|
||||||
// err = encoder.Encode(calendar.Data)
|
// err = encoder.Encode(calendar.Data)
|
||||||
// if err != nil {return}
|
// if err != nil {return}
|
||||||
//
|
//
|
||||||
// _, err = client.PutCalendarObject(ctx, m.Creds.CalendarPath, calendar.Data)
|
// _, err = client.PutCalendarObject(ctx, m.Creds.CalendarPath, calendar.Data)
|
||||||
// if err != nil {return}
|
// if err != nil {return}
|
||||||
|
|
||||||
// req := http.Request{
|
// req := http.Request{
|
||||||
// Method: "DELETE",
|
// Method: "DELETE",
|
||||||
// URL: url.Parse(m.Creds.URL + m.Creds.CalendarPath + delUID + ".isc"),
|
// URL: url.Parse(m.Creds.URL + m.Creds.CalendarPath + delUID + ".isc"),
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
|
|
||||||
parts := strings.Split(m.Creds.URL, "/")
|
parts := strings.Split(m.Creds.URL, "/")
|
||||||
baseURL := parts[0]+"//"+parts[2]
|
baseURL := parts[0] + "//" + parts[2]
|
||||||
// Create request
|
// Create request
|
||||||
req, err := http.NewRequest("DELETE", baseURL + m.Creds.CalendarPath + delUID + ".isc", nil)
|
req, err := http.NewRequest("DELETE", baseURL+m.Creds.CalendarPath+delUID+".isc", nil)
|
||||||
if err != nil {return}
|
if err != nil {
|
||||||
req.SetBasicAuth(m.Creds.Username, m.Creds.Password)
|
return
|
||||||
|
}
|
||||||
|
req.SetBasicAuth(m.Creds.Username, m.Creds.Password)
|
||||||
|
|
||||||
// Fetch Request
|
// Fetch Request
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {return}
|
if err != nil {
|
||||||
// resp.Body.Close()
|
return
|
||||||
defer resp.Body.Close()
|
}
|
||||||
|
// resp.Body.Close()
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Read Response Body
|
// Read Response Body
|
||||||
// respBody, err := ioutil.ReadAll(resp.Body)
|
// respBody, err := ioutil.ReadAll(resp.Body)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// // fmt.Println(err)
|
// // fmt.Println(err)
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if resp.Status == "204 No Content" {return nil}
|
if resp.Status == "204 No Content" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if resp.Status == "404 Not Found" {
|
if resp.Status == "404 Not Found" {
|
||||||
// Nextcloud Tasks create differend GUID for Vtodo and path
|
// Nextcloud Tasks create differend GUID for Vtodo and path
|
||||||
// TODO code fmt - Highway to for-if hell
|
// TODO code fmt - Highway to for-if hell
|
||||||
|
|
||||||
for _, calObj := range m.CalObjects {
|
for _, calObj := range m.CalObjects {
|
||||||
for _, eventComponent:= range (*calObj.Data).Children {
|
for _, eventComponent := range (*calObj.Data).Children {
|
||||||
event := ical.Event{Component: eventComponent}
|
event := ical.Event{Component: eventComponent}
|
||||||
if event.Name == "VTODO" {
|
if event.Name == "VTODO" {
|
||||||
// eventUID,_ := (*event).Props["UID"][0].Text()
|
// eventUID,_ := (*event).Props["UID"][0].Text()
|
||||||
|
|
||||||
eventUID,err := event.Props.Get(ical.PropUID).Text()
|
|
||||||
|
|
||||||
if err != nil {eventUID = ""}
|
eventUID, err := event.Props.Get(ical.PropUID).Text()
|
||||||
if (eventUID == delUID) {
|
|
||||||
|
if err != nil {
|
||||||
|
eventUID = ""
|
||||||
|
}
|
||||||
|
if eventUID == delUID {
|
||||||
//verify that it's indeed that event by comparing Name
|
//verify that it's indeed that event by comparing Name
|
||||||
sum1,err:=todo.Props.Get("SUMMARY").Text()
|
sum1, err := todo.Props.Get("SUMMARY").Text()
|
||||||
if err != nil {return err}
|
if err != nil {
|
||||||
sum2,err:=event.Props.Get("SUMMARY").Text()
|
return err
|
||||||
if err != nil {return err}
|
}
|
||||||
if sum1==sum2 {
|
sum2, err := event.Props.Get("SUMMARY").Text()
|
||||||
req2, err := http.NewRequest("DELETE", baseURL + calObj.Path, nil)
|
if err != nil {
|
||||||
if err != nil {return err}
|
return err
|
||||||
req2.SetBasicAuth(m.Creds.Username, m.Creds.Password)
|
}
|
||||||
|
if sum1 == sum2 {
|
||||||
// Fetch Request
|
req2, err := http.NewRequest("DELETE", baseURL+calObj.Path, nil)
|
||||||
resp2, err := client.Do(req2)
|
if err != nil {
|
||||||
if err != nil {return err}
|
return err
|
||||||
|
}
|
||||||
|
req2.SetBasicAuth(m.Creds.Username, m.Creds.Password)
|
||||||
|
|
||||||
defer resp2.Body.Close()
|
// Fetch Request
|
||||||
if resp2.Status == "204 No Content" {return nil} else {return errors.New("Can't delete, response status: "+resp2.Status+".")}
|
resp2, err := client.Do(req2)
|
||||||
|
if err != nil {
|
||||||
//TODO exit for loop
|
return err
|
||||||
// return errors.New("test")
|
}
|
||||||
}
|
|
||||||
|
defer resp2.Body.Close()
|
||||||
|
if resp2.Status == "204 No Content" {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.New("Can't delete, response status: " + resp2.Status + ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO exit for loop
|
||||||
|
// return errors.New("test")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return errors.New("test")
|
// return errors.New("test")
|
||||||
}
|
}
|
||||||
time.Sleep(2*time.Second) //TODO DEBUG RM ME
|
time.Sleep(2 * time.Second) //TODO DEBUG RM ME
|
||||||
// Display Results
|
// Display Results
|
||||||
// fmt.Println("response Status : ", resp.Status)
|
// fmt.Println("response Status : ", resp.Status)
|
||||||
// fmt.Println("response Headers : ", resp.Header)
|
// fmt.Println("response Headers : ", resp.Header)
|
||||||
// fmt.Println("response Body : ", string(respBody))
|
// fmt.Println("response Body : ", string(respBody))
|
||||||
|
|
||||||
// return nil
|
// return nil
|
||||||
return errors.New("Can't delete, response status: "+resp.Status+".")
|
return errors.New("Can't delete, response status: " + resp.Status + ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (m model) EditTodo(todo ical.Event) (err error) {
|
func (m model) EditTodo(todo ical.Event) (err error) {
|
||||||
//TODO is there proper edit function ???
|
//TODO is there proper edit function ???
|
||||||
// uid,err := todo.Props.Get(ical.PropUID).Text()
|
// uid,err := todo.Props.Get(ical.PropUID).Text()
|
||||||
// if err != nil {return}
|
// if err != nil {return}
|
||||||
|
|
||||||
err = m.DelTodo(todo)
|
err = m.DelTodo(todo)
|
||||||
if err != nil {return}
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = m.UploadTodo(todo)
|
err = m.UploadTodo(todo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//TODO panic, we deleted but cant upload event. Need to save somehow. Or at least write debug string with parameters so we can re-add manually in that case
|
//TODO panic, we deleted but cant upload event. Need to save somehow. Or at least write debug string with parameters so we can re-add manually in that case
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
// TODO
|
||||||
func (m model) CompleteTodo(todo ical.Event) (err error) {
|
func (m model) CompleteTodo(todo ical.Event) (err error) {
|
||||||
|
|
||||||
//TODO if it repitable - Repeate, otherwise complete it
|
//if it repitable - update DUE time, otherwise complete it
|
||||||
|
|
||||||
if todo.Props["RRULE"] != nil {
|
if todo.Props["RRULE"] != nil {
|
||||||
var rOptions *rrule.ROption
|
var rOptions *rrule.ROption
|
||||||
rOptions, err = todo.Props.RecurrenceRule()
|
rOptions, err = todo.Props.RecurrenceRule()
|
||||||
if err != nil {return}
|
if err != nil {
|
||||||
offset := rOptions.Interval
|
return
|
||||||
var currentDUE time.Time
|
|
||||||
currentDUE,err = todo.Props.DateTime("DUE",nil)
|
|
||||||
if err != nil {return}
|
|
||||||
switch rOptions.Freq {
|
|
||||||
case rrule.DAILY:
|
|
||||||
currentDUE = currentDUE.AddDate(0,0,offset)
|
|
||||||
case rrule.WEEKLY:
|
|
||||||
currentDUE = currentDUE.AddDate(0,0,7*offset)
|
|
||||||
case rrule.MONTHLY:
|
|
||||||
currentDUE = currentDUE.AddDate(0,offset,0)
|
|
||||||
case rrule.YEARLY:
|
|
||||||
currentDUE = currentDUE.AddDate(offset,0,0)
|
|
||||||
case rrule.HOURLY: //TODO didn't debug
|
|
||||||
currentDUE = currentDUE.Add(time.Hour * time.Duration(offset))
|
|
||||||
case rrule.MINUTELY: //TODO didn't debug
|
|
||||||
currentDUE = currentDUE.Add(time.Minute * time.Duration(offset))
|
|
||||||
case rrule.SECONDLY: //TODO didn't debug
|
|
||||||
currentDUE = currentDUE.Add(time.Second * time.Duration(offset))
|
|
||||||
}
|
}
|
||||||
todo.Props.SetDateTime("DUE",currentDUE)
|
offset := rOptions.Interval
|
||||||
|
var currentDUE time.Time
|
||||||
|
currentDUE, err = todo.Props.DateTime("DUE", nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch rOptions.Freq {
|
||||||
|
case rrule.DAILY:
|
||||||
|
currentDUE = currentDUE.AddDate(0, 0, offset)
|
||||||
|
case rrule.WEEKLY:
|
||||||
|
currentDUE = currentDUE.AddDate(0, 0, 7*offset)
|
||||||
|
case rrule.MONTHLY:
|
||||||
|
currentDUE = currentDUE.AddDate(0, offset, 0)
|
||||||
|
case rrule.YEARLY:
|
||||||
|
currentDUE = currentDUE.AddDate(offset, 0, 0)
|
||||||
|
case rrule.HOURLY: //TODO didn't debug
|
||||||
|
currentDUE = currentDUE.Add(time.Hour * time.Duration(offset))
|
||||||
|
case rrule.MINUTELY: //TODO didn't debug
|
||||||
|
currentDUE = currentDUE.Add(time.Minute * time.Duration(offset))
|
||||||
|
case rrule.SECONDLY: //TODO didn't debug
|
||||||
|
currentDUE = currentDUE.Add(time.Second * time.Duration(offset))
|
||||||
|
//TODO case if nothing changed
|
||||||
|
}
|
||||||
|
|
||||||
|
todo.Props.SetDateTime("DUE", currentDUE)
|
||||||
} else {
|
} else {
|
||||||
todo.Props.SetText(ical.PropStatus,"COMPLETED")
|
todo.Props.SetText(ical.PropStatus, "COMPLETED")
|
||||||
todo.Props.SetText(ical.PropPercentComplete,"100")
|
todo.Props.SetText(ical.PropPercentComplete, "100")
|
||||||
todo.Props.SetDateTime(ical.PropCompleted,time.Now())
|
todo.Props.SetDateTime(ical.PropCompleted, time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.EditTodo(todo)
|
err = m.EditTodo(todo)
|
||||||
if err != nil {return}
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
21
keyring.go
21
keyring.go
@ -43,7 +43,7 @@ const (
|
|||||||
// log.Println(decoded)
|
// log.Println(decoded)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
//TODO we can make custom type e.g. SaveData - with []string and put everything saved there. OR we can make the thing like in inputs[variable] - to make it convinient
|
//TODO we can make custom type e.g. SaveData - with []string and put everything saved there. OR we can make the thing like in inputs[variable] - to make it convinient
|
||||||
|
|
||||||
//TODO inconsistend global funcs (or is it called 'exported funcs?')
|
//TODO inconsistend global funcs (or is it called 'exported funcs?')
|
||||||
|
|
||||||
@ -95,18 +95,19 @@ func getCredentialsFromKeyring_wrapper() (url, login, password, calendar string,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCredentialsFromKeyring() (Credentials, error) {
|
||||||
func getCredentialsFromKeyring() (Credentials,error) {
|
|
||||||
//TODO inconsistent approach compared to caldav.go
|
//TODO inconsistent approach compared to caldav.go
|
||||||
url,username,password,calendar,err := getCredentialsFromKeyring_wrapper()
|
url, username, password, calendar, err := getCredentialsFromKeyring_wrapper()
|
||||||
if err != nil {return Credentials{},err}
|
if err != nil {
|
||||||
|
return Credentials{}, err
|
||||||
|
}
|
||||||
return Credentials{
|
return Credentials{
|
||||||
URL:url,
|
URL: url,
|
||||||
Username:username,
|
Username: username,
|
||||||
Password:password,
|
Password: password,
|
||||||
CalendarPath:calendar,
|
CalendarPath: calendar,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// func debugKeyring() {
|
// func debugKeyring() {
|
||||||
|
44
main.go
44
main.go
@ -1,10 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"net/http"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
// "sync"
|
// "sync"
|
||||||
// "time"
|
// "time"
|
||||||
|
|
||||||
@ -13,12 +13,16 @@ import (
|
|||||||
|
|
||||||
"github.com/emersion/go-webdav/caldav"
|
"github.com/emersion/go-webdav/caldav"
|
||||||
// "slices"
|
// "slices"
|
||||||
|
|
||||||
|
|
||||||
// "strconv"
|
// "strconv"
|
||||||
// "github.com/charmbracelet/bubbles/list"
|
// "github.com/charmbracelet/bubbles/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//TODO autoupdate?
|
||||||
|
//TODO predefined filters (missed tasks, upcoming important tasks, tasks without tag, tasks without priority)
|
||||||
|
//TODO custom filters
|
||||||
|
//TODO alarms
|
||||||
|
//TODO search in all tasks
|
||||||
|
|
||||||
// var waitGroup sync.WaitGroup
|
// var waitGroup sync.WaitGroup
|
||||||
|
|
||||||
func errHandler(err error, message string) {
|
func errHandler(err error, message string) {
|
||||||
@ -31,7 +35,7 @@ func errHandler(err error, message string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
options, err := ParseOptions()
|
options, err := ParseOptions()
|
||||||
errHandler(err, "Error parsing options")
|
errHandler(err, "Error parsing options")
|
||||||
m := InitModel()
|
m := InitModel()
|
||||||
@ -48,8 +52,8 @@ func main() {
|
|||||||
errHandler(err, "Unexpected error (we couldn't initiate WebDAV/CalDAV client)")
|
errHandler(err, "Unexpected error (we couldn't initiate WebDAV/CalDAV client)")
|
||||||
calendars, err = GetCalendars()
|
calendars, err = GetCalendars()
|
||||||
errHandler(err, "Error getting calendars (incorrect url/login/password)")
|
errHandler(err, "Error getting calendars (incorrect url/login/password)")
|
||||||
|
|
||||||
//TODO crushs START
|
//TODO BUG crush START
|
||||||
var found bool
|
var found bool
|
||||||
// var calPath string
|
// var calPath string
|
||||||
if options.Calendar == "" {
|
if options.Calendar == "" {
|
||||||
@ -57,33 +61,30 @@ func main() {
|
|||||||
// m.ActiveWindow = "login"
|
// m.ActiveWindow = "login"
|
||||||
}
|
}
|
||||||
if options.Calendar != "" {
|
if options.Calendar != "" {
|
||||||
for _,calendar := range calendars {
|
for _, calendar := range calendars {
|
||||||
if calendar.Name == options.Calendar {
|
if calendar.Name == options.Calendar {
|
||||||
found = true
|
found = true
|
||||||
// calPath = calendar.Path
|
// calPath = calendar.Path
|
||||||
m.Creds.CalendarPath = calendar.Path
|
m.Creds.CalendarPath = calendar.Path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ! found {
|
if !found {
|
||||||
fmt.Println("we don't have calendar ", options.Calendar, ". We have:")
|
fmt.Println("we don't have calendar ", options.Calendar, ". We have:")
|
||||||
for _,calendar := range calendars {
|
for _, calendar := range calendars {
|
||||||
fmt.Println(calendar.Name)
|
fmt.Println(calendar.Name)
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// fmt.Println(m)
|
// fmt.Println(m)
|
||||||
m.LoginToCalendar()
|
m.LoginToCalendar()
|
||||||
m.CalendarToTodo()
|
m.CalendarToTodo()
|
||||||
|
|
||||||
|
//TODO BUG crush End
|
||||||
//TODO crushs End
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO I'm on a highway to (IfElse) hell!
|
//TODO I'm on a highway to (IfElse) hell!
|
||||||
//TODO probably need to do more careful debug
|
//TODO probably need to do more careful debug
|
||||||
creds, err := getCredentialsFromKeyring()
|
creds, err := getCredentialsFromKeyring()
|
||||||
m.Creds = creds
|
m.Creds = creds
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -106,17 +107,17 @@ func main() {
|
|||||||
m.ActiveWindow = "today"
|
m.ActiveWindow = "today"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//DEBUG stuff
|
//DEBUG stuff
|
||||||
// task, err := CreateTodo("testName","description",3,time.Now())
|
// task, err := CreateTodo("testName","description",3,time.Now())
|
||||||
// errHandler(err,"test fail")
|
// errHandler(err,"test fail")
|
||||||
// err = m.UploadTodo(task)
|
// err = m.UploadTodo(task)
|
||||||
// errHandler(err,"test fail2")
|
// errHandler(err,"test fail2")
|
||||||
|
// m.ActiveWindow = "addTODO"
|
||||||
//DEBUG stuff
|
//DEBUG stuff
|
||||||
|
|
||||||
//TODO if task have alarm - make a notification / play sound...
|
//TODO if task have alarm - make a notification / play sound...
|
||||||
@ -128,5 +129,4 @@ func main() {
|
|||||||
|
|
||||||
// fmt.Println(m.)
|
// fmt.Println(m.)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
13
options.go
13
options.go
@ -1,21 +1,23 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/projectdiscovery/goflags"
|
"github.com/projectdiscovery/goflags"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var onceOptions sync.Once
|
var onceOptions sync.Once
|
||||||
var options = &Options{}
|
var options = &Options{}
|
||||||
|
|
||||||
|
//TODO NEW FEATURE - batch/simple add tasks via CLI
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
URL string
|
URL string
|
||||||
// Threads int
|
// Threads int
|
||||||
// Verbose bool
|
// Verbose bool
|
||||||
SkipSave bool
|
SkipSave bool
|
||||||
Calendar string
|
Calendar string
|
||||||
|
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
@ -68,8 +70,9 @@ func (options *Options) SanityCheck() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Proxy != "" {os.Setenv("HTTPS_PROXY", options.Proxy)}
|
if options.Proxy != "" {
|
||||||
|
os.Setenv("HTTPS_PROXY", options.Proxy)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
300
tui-addTodo.go
Normal file
300
tui-addTodo.go
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
// "github.com/charmbracelet/bubbles/list"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"time"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
name = iota
|
||||||
|
dueDate
|
||||||
|
|
||||||
|
description
|
||||||
|
priority
|
||||||
|
//TODO add support for subtasks
|
||||||
|
alarmOffset
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dueTimeMinute = iota
|
||||||
|
dueTimeHour
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m model) RenderAddTodo() string {
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`%s
|
||||||
|
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
|
||||||
|
%s
|
||||||
|
%s %0-0s%s
|
||||||
|
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
|
||||||
|
|
||||||
|
%s
|
||||||
|
`,
|
||||||
|
|
||||||
|
inputStyle.Width(30).Align(lipgloss.Center).Render("Create TODO"),
|
||||||
|
inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("Name"),
|
||||||
|
m.todoAddInputs[name].View(),
|
||||||
|
inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("Due Time"),
|
||||||
|
m.todoAddInputs[dueDate].View(),
|
||||||
|
m.todoAddInputsTime[dueTimeHour].View(),
|
||||||
|
m.todoAddInputsTime[dueTimeMinute].View(),
|
||||||
|
inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("Description"),
|
||||||
|
m.todoAddInputs[description].View(),
|
||||||
|
inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("Priority"),
|
||||||
|
//TODO add support for subtasks
|
||||||
|
m.todoAddInputs[priority].View(),
|
||||||
|
inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("Alarm offset"),
|
||||||
|
m.todoAddInputs[alarmOffset].View(),
|
||||||
|
buttonStyle.Render(" Add "),
|
||||||
|
)
|
||||||
|
// .Align(lipgloss.Center).BorderStyle(lipgloss.NormalBorder())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) nextTODOInput() {
|
||||||
|
|
||||||
|
m.todoAddInputsTime[0].Blur()
|
||||||
|
m.todoAddInputsTime[1].Blur()
|
||||||
|
m.todoAddInputs[m.focused].Blur()
|
||||||
|
|
||||||
|
if m.focused == len(m.todoAddInputs)-1 {
|
||||||
|
|
||||||
|
if m.btnFocus == true {
|
||||||
|
m.btnFocus = false
|
||||||
|
buttonStyle = lipgloss.NewStyle()
|
||||||
|
m.focused = (m.focused + 1) % len(m.todoAddInputs)
|
||||||
|
//TODO do smth with blinking cursor?
|
||||||
|
} else {
|
||||||
|
buttonStyle = buttonStyle.Background(lipgloss.Color("#7D56F4"))
|
||||||
|
m.btnFocus = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// buttonStyle = lipgloss.NewStyle()
|
||||||
|
m.focused = (m.focused + 1) % len(m.todoAddInputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
m.todoAddInputs[m.focused].Focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevInput focuses the previous input field
|
||||||
|
func (m *model) prevTODOInput() {
|
||||||
|
m.todoAddInputs[m.focused].Blur()
|
||||||
|
if m.focused == len(m.todoAddInputs)-1 {
|
||||||
|
if m.btnFocus == true {
|
||||||
|
m.btnFocus = false
|
||||||
|
buttonStyle = lipgloss.NewStyle()
|
||||||
|
//TODO do smth with blinking cursor?
|
||||||
|
// m.focused = (m.focused + 1) % len(m.loginInputs)
|
||||||
|
} else {
|
||||||
|
m.focused--
|
||||||
|
// Wrap around
|
||||||
|
if m.focused < 0 {
|
||||||
|
m.focused = len(m.todoAddInputs) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.focused--
|
||||||
|
// Wrap around
|
||||||
|
if m.focused < 0 {
|
||||||
|
m.focused = len(m.todoAddInputs) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.todoAddInputs[m.focused].Focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) AddTODOtoList() (err error) {
|
||||||
|
|
||||||
|
//TODO BUG - we can get another due date to timezon differs from UTC
|
||||||
|
//TODO BUG - need to fix `PRIORITY;VALUE=TEXT:0` - shouldn't be TEXT thing
|
||||||
|
now := time.Now()
|
||||||
|
// time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||||
|
// _, offset := now.Zone() // substract timezone offset because after `now.Truncate(24 * time.Hour)` it makes this -> 2024-07-08 03:00:00 +0300 MSK. so start of a day still in UTC??!? wtf
|
||||||
|
// timezoneOffset := time.Duration(-offset) * time.Second
|
||||||
|
// now = now.Add(timezoneOffset)
|
||||||
|
now = time.Date(now.Year(), now.Month(), now.Day(),0,0,0,0, now.Location())
|
||||||
|
// now := time.Now().UTC()
|
||||||
|
todoInfo := TodoInterface{
|
||||||
|
name: m.todoAddInputs[name].Value(),
|
||||||
|
description: m.todoAddInputs[description].Value(),
|
||||||
|
// priority: priorityTmp,
|
||||||
|
// dueTime: time.Now(),
|
||||||
|
// alarmOffset: m.todoAddInputs[alarmOffset].Value(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var dueDateTime time.Time
|
||||||
|
|
||||||
|
m.todoAddInputs[dueDate].Value()
|
||||||
|
|
||||||
|
|
||||||
|
//t\d
|
||||||
|
tdigitRegexp := regexp.MustCompile(`t\d{1,2}`) //tXX
|
||||||
|
digitRegexp := regexp.MustCompile(`\d{1,2}`) // XX
|
||||||
|
dayMonthRegexp := regexp.MustCompile(`\d{1,2}\.\d{1,2}`) // xx.xx
|
||||||
|
fullDateRegexp := regexp.MustCompile(`\d{1,2}\.\d{1,2}\.\d{2,4}`) //xx.xx.xxxx
|
||||||
|
|
||||||
|
//Date
|
||||||
|
date := m.todoAddInputs[dueDate].Value()
|
||||||
|
switch {
|
||||||
|
// case "":
|
||||||
|
//no due date
|
||||||
|
case date == "t":
|
||||||
|
//if t = today (without time)
|
||||||
|
// now := time.Now()
|
||||||
|
dueDateTime = now
|
||||||
|
// dueDateTime = now.Truncate(24 * time.Hour)
|
||||||
|
// dueDateTime = now.Truncate(24 * time.Hour).UTC()
|
||||||
|
// return errors.New(fmt.Sprint(offset))
|
||||||
|
case tdigitRegexp.MatchString(date):
|
||||||
|
//if tX = today + x days (without time) //TODO notify user about this feature somehow
|
||||||
|
// now := time.Now()
|
||||||
|
addDays,err := strconv.Atoi(strings.TrimPrefix(date,"t"))
|
||||||
|
|
||||||
|
dueDateTime = now
|
||||||
|
// dueDateTime = now.Truncate(24 * time.Hour)
|
||||||
|
if err == nil {dueDateTime = dueDateTime.AddDate(0,0, addDays)
|
||||||
|
} else {return errors.New("Can't convert tXX date. Examples: t1, t7, t14")} //TODO return error?
|
||||||
|
|
||||||
|
|
||||||
|
case fullDateRegexp.MatchString(date):
|
||||||
|
//if 12.08.2024 / = you know
|
||||||
|
// if 12.08.24 = you know
|
||||||
|
sep := strings.Split(date, ".")
|
||||||
|
year,err1 := strconv.Atoi(sep[2])
|
||||||
|
monthInt,err2 := strconv.Atoi(sep[1])
|
||||||
|
day,err3 := strconv.Atoi(sep[0])
|
||||||
|
|
||||||
|
if year <=2000 {year+=2000}
|
||||||
|
// year := time.Year(yearInt)
|
||||||
|
month := time.Month(monthInt)
|
||||||
|
// day := time.Day(dayInt)
|
||||||
|
|
||||||
|
//TODO if it's not full year???
|
||||||
|
if err1== nil&&err2== nil&&err3 == nil {
|
||||||
|
dueDateTime = time.Date(year, month,day, 0, 0, 0, 0, time.Local)
|
||||||
|
|
||||||
|
}
|
||||||
|
case dayMonthRegexp.MatchString(date):
|
||||||
|
//if 12.08 = 12th day of 8th month of this year
|
||||||
|
currentYear := now.Year()
|
||||||
|
sep := strings.Split(date, ".")
|
||||||
|
monthInt,err2 := strconv.Atoi(sep[1])
|
||||||
|
month := time.Month(monthInt)
|
||||||
|
day,err3 := strconv.Atoi(sep[0])
|
||||||
|
if err2== nil&&err3 == nil {
|
||||||
|
dueDateTime = time.Date(currentYear, month,day, 0, 0, 0, 0, time.Local)
|
||||||
|
}
|
||||||
|
case digitRegexp.MatchString(date):
|
||||||
|
//if 12 = 12th day of this month of this year
|
||||||
|
// now := time.Now()
|
||||||
|
currentMonth := now.Month()
|
||||||
|
currentYear := now.Year()
|
||||||
|
intDate,err := strconv.Atoi(date)
|
||||||
|
if err != nil {_=err} //TODO return error?
|
||||||
|
dueDateTime = time.Date(currentYear, currentMonth, intDate, 0, 0, 0, 0, time.Local)
|
||||||
|
// case
|
||||||
|
// default:
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Time
|
||||||
|
//if hour!="" add to date
|
||||||
|
//if monute!="" add to date
|
||||||
|
hour,err1 := strconv.Atoi(m.todoAddInputsTime[1].Value())
|
||||||
|
minute,err2 := strconv.Atoi(m.todoAddInputsTime[0].Value())
|
||||||
|
if (hour != 0 && err1 ==nil ) || (err2 == nil && minute != 0) {
|
||||||
|
if dueDateTime.IsZero() { //set to today
|
||||||
|
dueDateTime = now
|
||||||
|
// dueDateTime = now.Truncate(24 * time.Hour)
|
||||||
|
}
|
||||||
|
dueDateTime = dueDateTime.Add(time.Hour * time.Duration(hour) + time.Minute * time.Duration(minute))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dueDateTime.IsZero() {
|
||||||
|
todoInfo.dueTime = dueDateTime
|
||||||
|
// todoInfo.dueTime = dueDateTime.Add(timezoneOffset)
|
||||||
|
// return errors.New(fmt.Sprint(todoInfo.dueTime)) //TODO RM ME DEBUG
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
todoInfo.priority = 0
|
||||||
|
var priorityTmp int
|
||||||
|
if m.todoAddInputs[priority].Value() != "" {
|
||||||
|
var err error
|
||||||
|
priorityTmp,err = strconv.Atoi(m.todoAddInputs[priority].Value())
|
||||||
|
if err != nil {return err} //TODO debug. do like in next line
|
||||||
|
// if err != nil {priorityTmp = 0}
|
||||||
|
} else {}
|
||||||
|
|
||||||
|
alarmOffsetTmp := m.todoAddInputs[alarmOffset].Value()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if priorityTmp != 0 {
|
||||||
|
todoInfo.priority = priorityTmp
|
||||||
|
}
|
||||||
|
if !dueDateTime.IsZero() && alarmOffsetTmp != "" {
|
||||||
|
todoInfo.alarmOffset = alarmOffsetTmp
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
task, err := CreateTodo(todoInfo)
|
||||||
|
// if err != nil {return m.errHandler(err,"Failed to creating todo")}
|
||||||
|
if err != nil {return err}
|
||||||
|
err = m.UploadTodo(task)
|
||||||
|
// if err != nil {return m.errHandler(err,"Failed to uploade todo")}
|
||||||
|
if err != nil {return err}
|
||||||
|
err = m.UpdateTodos(task)
|
||||||
|
// if err != nil {return m.errHandler(err,"Failed updating todo list")} //Useless
|
||||||
|
if err != nil {return err}
|
||||||
|
//TODO deal with errors - append comment on what broken?
|
||||||
|
|
||||||
|
m.ActiveWindow = "today" //TODO remove to previus window
|
||||||
|
|
||||||
|
|
||||||
|
//TODO clean previous input
|
||||||
|
for i,_ := range m.todoAddInputsTime {
|
||||||
|
m.todoAddInputsTime[i].Blur()
|
||||||
|
m.todoAddInputsTime[i].Reset()
|
||||||
|
}
|
||||||
|
for i,_ := range m.todoAddInputs {
|
||||||
|
m.todoAddInputs[i].Blur()
|
||||||
|
m.todoAddInputs[i].Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
m.focused = 0
|
||||||
|
m.todoAddInputs[m.focused].Focus()
|
||||||
|
// m.todoAddInputsTime.Reset()
|
||||||
|
// m.todoAddInputs.Reset()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -3,9 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
// "github.com/charmbracelet/bubbles/list"
|
// "github.com/charmbracelet/bubbles/list"
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
"github.com/charmbracelet/bubbles/list"
|
"github.com/charmbracelet/bubbles/list"
|
||||||
"github.com/charmbracelet/bubbletea"
|
"github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
// "github.com/charmbracelet/lipgloss"
|
// "github.com/charmbracelet/lipgloss"
|
||||||
@ -20,7 +20,6 @@ var ( //Calendars choose
|
|||||||
quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
|
quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func (m model) RenderCalendarChooser() string {
|
func (m model) RenderCalendarChooser() string {
|
||||||
return m.calendarList.View()
|
return m.calendarList.View()
|
||||||
}
|
}
|
||||||
@ -52,13 +51,11 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list
|
|||||||
fmt.Fprint(w, fn(str))
|
fmt.Fprint(w, fn(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (m *model) CalendarToTodo() (err error) {
|
func (m *model) CalendarToTodo() (err error) {
|
||||||
|
|
||||||
m.LoggedIn = true
|
m.LoggedIn = true
|
||||||
m.GatherTodos()
|
m.GatherTodos()
|
||||||
m.ActiveWindow = "today"
|
m.ActiveWindow = "today"
|
||||||
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
100
tui-login.go
100
tui-login.go
@ -8,18 +8,19 @@ import (
|
|||||||
|
|
||||||
var docStyle = lipgloss.NewStyle().Margin(1, 2)
|
var docStyle = lipgloss.NewStyle().Margin(1, 2)
|
||||||
|
|
||||||
|
|
||||||
var loginStyle = lipgloss.NewStyle().Width(40).Align(lipgloss.Center).BorderStyle(lipgloss.NormalBorder())
|
var loginStyle = lipgloss.NewStyle().Width(40).Align(lipgloss.Center).BorderStyle(lipgloss.NormalBorder())
|
||||||
|
|
||||||
var inputStyle = lipgloss.NewStyle()
|
var inputStyle = lipgloss.NewStyle()
|
||||||
|
// var buttonStyle = lipgloss.NewStyle().Background(lipgloss.Color("#7D56F4"))
|
||||||
|
var buttonStyle = lipgloss.NewStyle()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
url = iota
|
url = iota
|
||||||
login
|
login
|
||||||
pass
|
pass
|
||||||
|
// btnFocus
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func (m model) RenderLogin() string {
|
func (m model) RenderLogin() string {
|
||||||
|
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
@ -46,55 +47,104 @@ func (m model) RenderLogin() string {
|
|||||||
m.loginInputs[login].View(),
|
m.loginInputs[login].View(),
|
||||||
inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("Password"),
|
inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("Password"),
|
||||||
m.loginInputs[pass].View(), //TODO hide
|
m.loginInputs[pass].View(), //TODO hide
|
||||||
inputStyle.Render("Continue ->"),
|
buttonStyle.Render("Continue ->"),
|
||||||
)
|
)
|
||||||
// .Align(lipgloss.Center).BorderStyle(lipgloss.NormalBorder())
|
// .Align(lipgloss.Center).BorderStyle(lipgloss.NormalBorder())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *model) nextInput() {
|
func (m *model) nextLoginInput() {
|
||||||
m.focused = (m.focused + 1) % len(m.loginInputs)
|
|
||||||
|
if m.focused == len(m.loginInputs)-1 {
|
||||||
|
|
||||||
|
if m.btnFocus == true {
|
||||||
|
m.btnFocus = false
|
||||||
|
buttonStyle = lipgloss.NewStyle()
|
||||||
|
m.focused = (m.focused + 1) % len(m.loginInputs)
|
||||||
|
//TODO do smth with blinking cursor?
|
||||||
|
} else {
|
||||||
|
buttonStyle = buttonStyle.Background(lipgloss.Color("#7D56F4"))
|
||||||
|
m.btnFocus = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// buttonStyle = lipgloss.NewStyle()
|
||||||
|
m.focused = (m.focused + 1) % len(m.loginInputs)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// m.focused = (m.focused + 1) % len(m.loginInputs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//btnFocus
|
||||||
|
// if m.focused == len(m.loginInputs) {
|
||||||
|
// buttonStyle = buttonStyle.Background(lipgloss.Color("#7D56F4"))
|
||||||
|
// } else {
|
||||||
|
// buttonStyle = lipgloss.NewStyle()
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevInput focuses the previous input field
|
// prevInput focuses the previous input field
|
||||||
func (m *model) prevInput() {
|
func (m *model) prevLoginInput() {
|
||||||
m.focused--
|
|
||||||
// Wrap around
|
|
||||||
if m.focused < 0 {
|
|
||||||
m.focused = len(m.loginInputs) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if m.focused == len(m.loginInputs)-1 {
|
||||||
|
if m.btnFocus == true {
|
||||||
|
m.btnFocus = false
|
||||||
|
buttonStyle = lipgloss.NewStyle()
|
||||||
|
//TODO do smth with blinking cursor?
|
||||||
|
// m.focused = (m.focused + 1) % len(m.loginInputs)
|
||||||
|
} else {
|
||||||
|
m.focused--
|
||||||
|
// Wrap around
|
||||||
|
if m.focused < 0 {
|
||||||
|
m.focused = len(m.loginInputs) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.focused--
|
||||||
|
// Wrap around
|
||||||
|
if m.focused < 0 {
|
||||||
|
m.focused = len(m.loginInputs) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (m *model) LoginToCalendar() (err error) {
|
func (m *model) LoginToCalendar() (err error) {
|
||||||
err = m.InitDAVclients()
|
err = m.InitDAVclients()
|
||||||
if err != nil { return }
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
m.Calendars, err = GetCalendars()
|
m.Calendars, err = GetCalendars()
|
||||||
if err != nil { return }
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
items := []list.Item{}
|
items := []list.Item{}
|
||||||
|
|
||||||
for _,calendar := range m.Calendars {
|
for _, calendar := range m.Calendars {
|
||||||
// fmt.Println(calendar.Name)
|
// fmt.Println(calendar.Name)
|
||||||
items = append(items,item(calendar.Name))
|
items = append(items, item(calendar.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// m.LoggedIn = true TODO after calendar choose
|
// m.LoggedIn = true TODO after calendar choose
|
||||||
|
|
||||||
const defaultWidth = 20
|
const defaultWidth = 20
|
||||||
const listHeight = 24
|
const listHeight = 24
|
||||||
l := list.New(items, itemDelegate{}, defaultWidth, listHeight)
|
l := list.New(items, itemDelegate{}, defaultWidth, listHeight)
|
||||||
l.Title = "Which To-Do list do we need?"
|
l.Title = "Which To-Do list do we need?"
|
||||||
l.SetShowStatusBar(false)
|
l.SetShowStatusBar(false)
|
||||||
l.SetFilteringEnabled(false)
|
l.SetFilteringEnabled(false)
|
||||||
l.Styles.Title = titleStyle
|
l.Styles.Title = titleStyle
|
||||||
l.Styles.PaginationStyle = paginationStyle
|
l.Styles.PaginationStyle = paginationStyle
|
||||||
l.Styles.HelpStyle = helpStyle
|
l.Styles.HelpStyle = helpStyle
|
||||||
|
|
||||||
m.calendarList = l
|
m.calendarList = l
|
||||||
m.ActiveWindow = "calendarChoose"
|
m.ActiveWindow = "calendarChoose"
|
||||||
|
|
||||||
|
m.btnFocus = false
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
139
tui-model.go
139
tui-model.go
@ -3,13 +3,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
// "fmt"
|
// "fmt"
|
||||||
"github.com/charmbracelet/bubbles/list"
|
"github.com/charmbracelet/bubbles/list"
|
||||||
"github.com/emersion/go-webdav/caldav"
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
|
"github.com/emersion/go-webdav/caldav"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type model struct {
|
type model struct {
|
||||||
Tabs []string
|
Tabs []string
|
||||||
// TabContent []string
|
// TabContent []string
|
||||||
@ -18,69 +15,131 @@ type model struct {
|
|||||||
|
|
||||||
TodayTab list.Model
|
TodayTab list.Model
|
||||||
TomorrowTab list.Model
|
TomorrowTab list.Model
|
||||||
|
|
||||||
calendarList list.Model
|
calendarList list.Model
|
||||||
calendarChoice string
|
calendarChoice string
|
||||||
|
|
||||||
|
todoAddInputs []textinput.Model
|
||||||
|
todoAddInputsTime []textinput.Model
|
||||||
|
addTimeFocus bool
|
||||||
loginInputs []textinput.Model
|
loginInputs []textinput.Model
|
||||||
focused int
|
focused int
|
||||||
|
btnFocus bool
|
||||||
err error
|
err error
|
||||||
|
|
||||||
Creds Credentials
|
Creds Credentials
|
||||||
Calendars []caldav.Calendar
|
Calendars []caldav.Calendar
|
||||||
CalObjects []caldav.CalendarObject
|
CalObjects []caldav.CalendarObject
|
||||||
|
|
||||||
errString string
|
errString string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Credentials struct {
|
type Credentials struct {
|
||||||
URL string
|
URL string
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
CalendarName string
|
CalendarName string
|
||||||
CalendarPath string
|
CalendarPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (m *model) CredentialsSave() (err error) {
|
func (m *model) CredentialsSave() (err error) {
|
||||||
|
|
||||||
//TODO some proper error handler in case if we cant save
|
//TODO some proper error handler in case if we cant save
|
||||||
err = storeCredentialsToKeyring(m.Creds.URL,m.Creds.Username,m.Creds.Password,m.Creds.CalendarPath)
|
err = storeCredentialsToKeyring(m.Creds.URL, m.Creds.Username, m.Creds.Password, m.Creds.CalendarPath)
|
||||||
//TODO add skip flag
|
//TODO add skip flag
|
||||||
if err != nil {return}
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func InitModel() model {
|
func InitModel() model {
|
||||||
var inputs []textinput.Model = make([]textinput.Model, 3)
|
var loginInputs1 []textinput.Model = make([]textinput.Model, 3)
|
||||||
inputs[url] = textinput.New()
|
loginInputs1[url] = textinput.New()
|
||||||
inputs[url].Placeholder = "https://nextcloud.example/remote.php/dav"
|
loginInputs1[url].Placeholder = "https://nextcloud.example/remote.php/dav"
|
||||||
inputs[url].Focus()
|
loginInputs1[url].Focus()
|
||||||
// inputs[url].CharLimit = 20
|
loginInputs1[url].Width = 30
|
||||||
inputs[url].Width = 30
|
loginInputs1[url].Prompt = ""
|
||||||
inputs[url].Prompt = ""
|
// loginInputs1[url].Validate = urlValidator //TODO
|
||||||
// inputs[url].Validate = urlValidator
|
|
||||||
|
|
||||||
inputs[login] = textinput.New()
|
loginInputs1[login] = textinput.New()
|
||||||
inputs[login].Placeholder = "username"
|
loginInputs1[login].Placeholder = "username"
|
||||||
// inputs[login].CharLimit = 5
|
loginInputs1[login].Width = 30
|
||||||
inputs[login].Width = 30
|
loginInputs1[login].Prompt = ""
|
||||||
inputs[login].Prompt = ""
|
|
||||||
// inputs[login].Validate = loginValidator
|
|
||||||
|
|
||||||
inputs[pass] = textinput.New() //TODO make pass hidden with "github.com/erikgeiser/promptkit/textinput"
|
loginInputs1[pass] = textinput.New()
|
||||||
inputs[pass].Placeholder = "MySecurePassword"
|
loginInputs1[pass].Placeholder = "MySecurePassword"
|
||||||
// inputs[pass].CharLimit = 3
|
loginInputs1[pass].Width = 30
|
||||||
inputs[pass].Width = 30
|
loginInputs1[pass].Prompt = ""
|
||||||
inputs[pass].Prompt = ""
|
loginInputs1[pass].EchoMode = 1 //list.EchoPassword
|
||||||
// inputs[pass].Validate = passValidator
|
|
||||||
|
var addTODOinputs []textinput.Model = make([]textinput.Model, 5)
|
||||||
|
//TODO NEW FEATURE - add config for default values in addTODO
|
||||||
|
//TODO NEW FEATURE - addTODO - add repeat field (and underlying support in caldav.go)
|
||||||
|
//TODO NEW FEATURE - addTODO - multiline input support
|
||||||
|
//TODO NEW FEATURE - addTODO - priority picker
|
||||||
|
// //TODO NEW FEATURE - addTODO - add support for subtasks
|
||||||
|
addTODOinputs[name] = textinput.New()
|
||||||
|
addTODOinputs[name].Placeholder = "Do task"
|
||||||
|
addTODOinputs[name].Focus()
|
||||||
|
addTODOinputs[name].Width = 30
|
||||||
|
addTODOinputs[name].Prompt = ""
|
||||||
|
|
||||||
|
addTODOinputs[dueDate] = textinput.New()
|
||||||
|
addTODOinputs[dueDate].Placeholder = "t/t1/12/12.08/12.08.2025" //TODO how
|
||||||
|
// ok, i want to choose from :
|
||||||
|
// no date
|
||||||
|
// today
|
||||||
|
// tomorrow
|
||||||
|
// custom - pick - https://github.com/EthanEFung/bubble-datepicker
|
||||||
|
// + time
|
||||||
|
addTODOinputs[dueDate].Width = 23
|
||||||
|
addTODOinputs[dueDate].CharLimit = 10
|
||||||
|
addTODOinputs[dueDate].Prompt = ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
addTODOinputs[description] = textinput.New()
|
||||||
|
addTODOinputs[description].Placeholder = "I need to do stuff"
|
||||||
|
// addTODOinputs[pass].CharLimit = 3
|
||||||
|
addTODOinputs[description].Width = 30
|
||||||
|
addTODOinputs[description].Prompt = ""
|
||||||
|
|
||||||
|
addTODOinputs[priority] = textinput.New()
|
||||||
|
addTODOinputs[priority].Placeholder = "0-3 (unknown,low,medium,high)"
|
||||||
|
// addTODOinputs[pass].CharLimit = 3
|
||||||
|
addTODOinputs[priority].Width = 30
|
||||||
|
addTODOinputs[priority].Prompt = ""
|
||||||
|
// suggestions := []string{"0","1","2","a3"}
|
||||||
|
// addTODOinputs[priority].SetSuggestions(suggestions)
|
||||||
|
|
||||||
|
addTODOinputs[alarmOffset] = textinput.New()
|
||||||
|
addTODOinputs[alarmOffset].Placeholder = "1d/1h/1m"
|
||||||
|
// addTODOinputs[pass].CharLimit = 3
|
||||||
|
addTODOinputs[alarmOffset].Width = 30
|
||||||
|
addTODOinputs[alarmOffset].Prompt = ""
|
||||||
|
|
||||||
|
|
||||||
|
var addTODOinputsTime []textinput.Model = make([]textinput.Model, 2)
|
||||||
|
addTODOinputsTime[dueTimeHour] = textinput.New()
|
||||||
|
addTODOinputsTime[dueTimeHour].Placeholder = "14" //TODO how
|
||||||
|
addTODOinputsTime[dueTimeHour].Width = 2
|
||||||
|
addTODOinputsTime[dueTimeHour].CharLimit = 2
|
||||||
|
addTODOinputsTime[dueTimeHour].Prompt = ""
|
||||||
|
|
||||||
|
addTODOinputsTime[dueTimeMinute] = textinput.New()
|
||||||
|
addTODOinputsTime[dueTimeMinute].Placeholder = "00" //TODO how
|
||||||
|
addTODOinputsTime[dueTimeMinute].Width = 2
|
||||||
|
addTODOinputsTime[dueTimeMinute].CharLimit = 2
|
||||||
|
addTODOinputsTime[dueTimeMinute].Prompt = ""
|
||||||
|
|
||||||
output := model{
|
output := model{
|
||||||
Tabs: []string{"Today", "Tomorrow", "Add"},
|
Tabs: []string{"Today", "Tomorrow", "Add"},
|
||||||
loginInputs: inputs,
|
loginInputs: loginInputs1,
|
||||||
focused: 0,
|
focused: 0,
|
||||||
err: nil,
|
err: nil,
|
||||||
|
todoAddInputs: addTODOinputs,
|
||||||
|
todoAddInputsTime: addTODOinputsTime,
|
||||||
// Creds: Credentials{"test","test","test","test","test"},
|
// Creds: Credentials{"test","test","test","test","test"},
|
||||||
// TabContent: []string{"ERROR?", "Mascara Tab", "Foundation Tab"},
|
// TabContent: []string{"ERROR?", "Mascara Tab", "Foundation Tab"},
|
||||||
}
|
}
|
||||||
|
230
tui-todo-keys.go
230
tui-todo-keys.go
@ -1,17 +1,27 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
||||||
"github.com/emersion/go-ical"
|
"github.com/emersion/go-ical"
|
||||||
// "time"
|
// "time"
|
||||||
"github.com/charmbracelet/bubbles/list"
|
|
||||||
"github.com/charmbracelet/bubbles/key"
|
"github.com/charmbracelet/bubbles/key"
|
||||||
|
"github.com/charmbracelet/bubbles/list"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
// "fmt"
|
||||||
|
// "io"
|
||||||
// "strings"
|
// "strings"
|
||||||
// "errors"
|
// "errors"
|
||||||
|
// "golang.org/x/term"
|
||||||
|
// "github.com/muesli/reflow/truncate"
|
||||||
|
// "strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// const (
|
||||||
|
// bullet = "•"
|
||||||
|
// ellipsis = "…"
|
||||||
|
// )
|
||||||
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
appStyle = lipgloss.NewStyle().Padding(1, 2)
|
appStyle = lipgloss.NewStyle().Padding(1, 2)
|
||||||
|
|
||||||
@ -25,10 +35,149 @@ var (
|
|||||||
Render
|
Render
|
||||||
)
|
)
|
||||||
|
|
||||||
// buttons
|
// type DefaultDelegate struct {
|
||||||
func (m model) newItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {
|
// ShowDescription bool
|
||||||
d := list.NewDefaultDelegate()
|
// Styles list.DefaultItemStyles
|
||||||
|
// UpdateFunc func(tea.Msg, *list.Model) tea.Cmd
|
||||||
|
// ShortHelpFunc func() []key.Binding
|
||||||
|
// FullHelpFunc func() [][]key.Binding
|
||||||
|
// // contains filtered or unexported fields
|
||||||
|
// }
|
||||||
|
var (
|
||||||
|
// titleStyle = lipgloss.NewStyle().MarginLeft(2)
|
||||||
|
itemStyleList = lipgloss.NewStyle().PaddingLeft(4)
|
||||||
|
// selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
|
||||||
|
// paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
|
||||||
|
// helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
|
||||||
|
// quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
|
||||||
|
)
|
||||||
|
// func (d DefaultDelegate) Height() int { return 1 }
|
||||||
|
// func (d DefaultDelegate) Spacing() int { return 0 }
|
||||||
|
// func (d DefaultDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
||||||
|
// func (d DefaultDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
||||||
|
// i, ok := listItem.(item)
|
||||||
|
// if !ok {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// str := fmt.Sprintf("%d. %s", index+1, i)
|
||||||
|
//
|
||||||
|
// // fn := itemStyleList.Foreground(TODO(listItem).PriorityColor).Render
|
||||||
|
// fn := itemStyle.Render
|
||||||
|
// // fn := itemStyleList.Foreground(lipgloss.Color(m.SelectedItem().(TODO).PriorityColor())).Render
|
||||||
|
// if index == m.Index() {
|
||||||
|
// fn = func(s ...string) string {
|
||||||
|
// // return selectedItemStyle.Render("> " + s)
|
||||||
|
// return selectedItemStyle.Render(s[index])
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, fn(str))
|
||||||
|
// }
|
||||||
|
// type filteredItems []filteredItem
|
||||||
|
// func (d DefaultDelegate) Render(w io.Writer, m *list.Model, index int, item list.Item) {
|
||||||
|
// var (
|
||||||
|
// title, desc string
|
||||||
|
// matchedRunes []int
|
||||||
|
// s = &d.Styles
|
||||||
|
// )
|
||||||
|
// width, height, _ := term.GetSize(0)
|
||||||
|
//
|
||||||
|
// if i, ok := item.(list.DefaultItem); ok {
|
||||||
|
// title = i.Title()
|
||||||
|
// desc = i.Description()
|
||||||
|
// } else {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if width <= 0 {
|
||||||
|
// // short-circuit
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Prevent text from exceeding list width
|
||||||
|
// textwidth := uint(width - s.NormalTitle.GetPaddingLeft() - s.NormalTitle.GetPaddingRight())
|
||||||
|
// title = truncate.StringWithTail(title, textwidth, ellipsis)
|
||||||
|
// if d.ShowDescription {
|
||||||
|
// var lines []string
|
||||||
|
// for i, line := range strings.Split(desc, "\n") {
|
||||||
|
// if i >= height-1 {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// lines = append(lines, truncate.StringWithTail(line, textwidth, ellipsis))
|
||||||
|
// }
|
||||||
|
// desc = strings.Join(lines, "\n")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Conditions
|
||||||
|
// var (
|
||||||
|
// isSelected = index == m.Index()
|
||||||
|
// emptyFilter = m.FilterState() == list.Filtering && m.FilterValue() == ""
|
||||||
|
// isFiltered = m.FilterState() == list.Filtering || m.FilterState() == list.FilterApplied
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// if m.IsFiltered && index < len(m.filteredItems) {
|
||||||
|
// // Get indices of matched characters
|
||||||
|
// matchedRunes = m.MatchesForItem(index)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if emptyFilter {
|
||||||
|
// title = s.DimmedTitle.Render(title)
|
||||||
|
// desc = s.DimmedDesc.Render(desc)
|
||||||
|
// } else if isSelected && m.FilterState() != Filtering {
|
||||||
|
// if isFiltered {
|
||||||
|
// // Highlight matches
|
||||||
|
// unmatched := s.SelectedTitle.Inline(true)
|
||||||
|
// matched := unmatched.Copy().Inherit(s.FilterMatch)
|
||||||
|
// title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched)
|
||||||
|
// }
|
||||||
|
// title = s.SelectedTitle.Render(title)
|
||||||
|
// desc = s.SelectedDesc.Render(desc)
|
||||||
|
// } else {
|
||||||
|
// if isFiltered {
|
||||||
|
// // Highlight matches
|
||||||
|
// unmatched := s.NormalTitle.Inline(true)
|
||||||
|
// matched := unmatched.Copy().Inherit(s.FilterMatch)
|
||||||
|
// title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched)
|
||||||
|
// }
|
||||||
|
// title = s.NormalTitle.Render(title)
|
||||||
|
// desc = s.NormalDesc.Render(desc)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if d.ShowDescription {
|
||||||
|
// fmt.Fprintf(w, "%s\n%s", title, desc)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// fmt.Fprintf(w, "%s", title)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// buttons
|
||||||
|
func (m *model) newItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {
|
||||||
|
d := list.NewDefaultDelegate()
|
||||||
|
// d := DefaultDelegate{}
|
||||||
|
m.ActiveWindow = "addTODO"
|
||||||
d.UpdateFunc = func(msg tea.Msg, ml *list.Model) tea.Cmd {
|
d.UpdateFunc = func(msg tea.Msg, ml *list.Model) tea.Cmd {
|
||||||
var title string
|
var title string
|
||||||
// var todoUID string
|
// var todoUID string
|
||||||
@ -47,30 +196,56 @@ func (m model) newItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {
|
|||||||
switch {
|
switch {
|
||||||
case key.Matches(msg, keys.choose):
|
case key.Matches(msg, keys.choose):
|
||||||
err := m.CompleteTodo(todoIcal)
|
err := m.CompleteTodo(todoIcal)
|
||||||
if err != nil {return errHandler_tui(err,"can't complete item")}
|
if err != nil {
|
||||||
|
return errHandler_tui(err, "can't complete item")
|
||||||
|
}
|
||||||
index := ml.Index()
|
index := ml.Index()
|
||||||
ml.RemoveItem(index)
|
ml.RemoveItem(index)
|
||||||
if len(ml.Items()) == 0 {
|
if len(ml.Items()) == 0 {
|
||||||
keys.choose.SetEnabled(false)
|
keys.choose.SetEnabled(false)
|
||||||
}
|
}
|
||||||
return ml.NewStatusMessage(statusMessageStyle("You chose " + title))
|
return ml.NewStatusMessage(statusMessageStyle("You completed " + title+"!"))
|
||||||
|
|
||||||
case key.Matches(msg, keys.remove):
|
case key.Matches(msg, keys.remove):
|
||||||
err := m.DelTodo(todoIcal)
|
err := m.DelTodo(todoIcal)
|
||||||
if err != nil {return errHandler_tui(err,"can't delete item")}
|
if err != nil {
|
||||||
|
return errHandler_tui(err, "can't delete item")
|
||||||
|
}
|
||||||
index := ml.Index()
|
index := ml.Index()
|
||||||
ml.RemoveItem(index)
|
ml.RemoveItem(index)
|
||||||
if len(ml.Items()) == 0 {
|
if len(ml.Items()) == 0 {
|
||||||
keys.remove.SetEnabled(false)
|
keys.remove.SetEnabled(false)
|
||||||
}
|
}
|
||||||
return ml.NewStatusMessage(statusMessageStyle("Deleted " + title))
|
return ml.NewStatusMessage(statusMessageStyle("Deleted " + title))
|
||||||
|
|
||||||
|
// case key.Matches(msg, keys.add):
|
||||||
|
//TODO IMPORTANT go to add todo form
|
||||||
|
// todoInfo := TodoInterface{
|
||||||
|
// name: "testName1",
|
||||||
|
// description: "description",
|
||||||
|
// priority: 3,
|
||||||
|
// dueTime: time.Now(),
|
||||||
|
// alarmOffset: "1h",
|
||||||
|
// }
|
||||||
|
// task, err := CreateTodo(todoInfo)
|
||||||
|
// if err != nil {return m.errHandler(err,"Failed to creating todo")}
|
||||||
|
// err = m.UploadTodo(task)
|
||||||
|
// if err != nil {return m.errHandler(err,"Failed to uploade todo")}
|
||||||
|
// err = m.UpdateTodos(task)
|
||||||
|
// if err != nil {return m.errHandler(err,"Failed updating todo list")} //Useless
|
||||||
|
|
||||||
|
// m.ActiveWindow = "addTODO"
|
||||||
|
// fmt.Println("ADSASDASDASD")
|
||||||
|
// return errHandler_tui(nil,"Failed updating todo list")
|
||||||
|
|
||||||
|
// return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
help := []key.Binding{keys.choose, keys.remove}
|
help := []key.Binding{keys.choose, keys.remove, keys.add}
|
||||||
|
|
||||||
d.ShortHelpFunc = func() []key.Binding {
|
d.ShortHelpFunc = func() []key.Binding {
|
||||||
return help
|
return help
|
||||||
@ -80,25 +255,52 @@ func (m model) newItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {
|
|||||||
return [][]key.Binding{help}
|
return [][]key.Binding{help}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// d.Render = func(w io.Writer, m list.Model, index int, listItem list.Item) {
|
||||||
|
//
|
||||||
|
// i, ok := listItem.(item)
|
||||||
|
// if !ok {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// str := fmt.Sprintf("%d. %s", index+1, i)
|
||||||
|
//
|
||||||
|
// fn := itemStyle.Render
|
||||||
|
// if index == m.Index() {
|
||||||
|
// fn = func(s string) string {
|
||||||
|
// return lipgloss.NewStyle().PaddingLeft(2).Foreground(listItem.PriorityColor)
|
||||||
|
// // .Render("> " + s)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, fn(str))
|
||||||
|
// }
|
||||||
|
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type delegateKeyMap struct {
|
type delegateKeyMap struct {
|
||||||
choose key.Binding
|
choose key.Binding
|
||||||
remove key.Binding
|
remove key.Binding
|
||||||
|
add key.Binding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func newDelegateKeyMap() *delegateKeyMap {
|
func newDelegateKeyMap() *delegateKeyMap {
|
||||||
return &delegateKeyMap{
|
return &delegateKeyMap{
|
||||||
choose: key.NewBinding(
|
choose: key.NewBinding(
|
||||||
key.WithKeys("enter"),
|
key.WithKeys("enter"),
|
||||||
key.WithHelp("enter", "choose"),
|
key.WithHelp("enter", "complete"),
|
||||||
),
|
),
|
||||||
remove: key.NewBinding(
|
remove: key.NewBinding(
|
||||||
key.WithKeys("x", "backspace"),
|
key.WithKeys("backspace", "backspace"),
|
||||||
key.WithHelp("x", "delete"),
|
key.WithHelp("backspace", "delete"),
|
||||||
),
|
),
|
||||||
|
add: key.NewBinding(
|
||||||
|
key.WithKeys("a"),
|
||||||
|
key.WithHelp("a", "Add TODO"),
|
||||||
|
),
|
||||||
|
//TODO PRIORITY FEATURE - edit TODO button
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
301
tui-todo.go
301
tui-todo.go
@ -1,68 +1,177 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// "errors"
|
||||||
"github.com/emersion/go-ical"
|
|
||||||
"time"
|
|
||||||
"github.com/charmbracelet/bubbles/list"
|
"github.com/charmbracelet/bubbles/list"
|
||||||
|
"github.com/emersion/go-ical"
|
||||||
"strings"
|
"strings"
|
||||||
"errors"
|
"time"
|
||||||
|
"sort"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type TODO ical.Event
|
type TODO ical.Event
|
||||||
|
|
||||||
func (i TODO) Title() string {
|
func (i TODO) Title() string {
|
||||||
out,err := i.Props.Get(ical.PropSummary).Text()
|
|
||||||
if err != nil {return "<EMPTY>"}
|
|
||||||
|
priority := "? "
|
||||||
|
prioInt, err := i.Props.Get("PRIORITY").Int()
|
||||||
|
var prioStr string
|
||||||
|
if err != nil {
|
||||||
|
prioStr, err = i.Props.Get("PRIORITY").Text()
|
||||||
|
if err == nil {
|
||||||
|
prioInt, err = strconv.Atoi(prioStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
switch prioInt{
|
||||||
|
case 9:
|
||||||
|
priority = "" //Low - Blue
|
||||||
|
case 5:
|
||||||
|
priority = "❕ " //Mid - Yellow
|
||||||
|
case 1:
|
||||||
|
priority = "❗ " //High - Red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time1, err := i.Props.DateTime("DUE",nil)
|
||||||
|
// today := time.Now().Truncate(24 * time.Hour)
|
||||||
|
var dueTime,additionalZero string
|
||||||
|
if (time1.Hour() != 0) || (time1.Minute() != 0) {
|
||||||
|
|
||||||
|
if time1.Minute() <=9 {
|
||||||
|
additionalZero = "0"
|
||||||
|
}
|
||||||
|
dueTime = fmt.Sprint(time1.Hour())+":"+additionalZero+fmt.Sprint(time1.Minute()) + " " //TODO move to sprintf
|
||||||
|
// if time1.Minute() == 0 {
|
||||||
|
// dueTime += "0 "
|
||||||
|
// } else {dueTime += " "}
|
||||||
|
}
|
||||||
|
|
||||||
|
// timer, err := i.Props.Get("DUE").Int() //TODO
|
||||||
|
// currentDUE = currentDUE.Add(time.Hour * time.Duration(offset))
|
||||||
|
//TODO if both times exists
|
||||||
|
// if
|
||||||
|
// timer+"->"+dueTime +" |"
|
||||||
|
|
||||||
|
out, err := i.Props.Get(ical.PropSummary).Text()
|
||||||
|
if err != nil {
|
||||||
|
return "<EMPTY>"
|
||||||
|
}
|
||||||
|
return priority + dueTime + out
|
||||||
|
}
|
||||||
|
func (i TODO) UID() string {
|
||||||
|
out, err := i.Props.Get(ical.PropUID).Text()
|
||||||
|
if err != nil {
|
||||||
|
return "<EMPTY>"
|
||||||
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
func (i TODO) UID() string {
|
|
||||||
out,err := i.Props.Get(ical.PropUID).Text()
|
func (i TODO) PriorityColor() lipgloss.Color {
|
||||||
if err != nil {return "<EMPTY>"}
|
clr, err := i.Props.Get("PRIORITY").Int()
|
||||||
return out
|
c := lipgloss.Color("255") //White
|
||||||
|
if err != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
switch clr{
|
||||||
|
case 9:
|
||||||
|
c = lipgloss.Color("27") //Blue
|
||||||
|
case 5:
|
||||||
|
c = lipgloss.Color("220") //Yellow
|
||||||
|
case 1:
|
||||||
|
// c = lipgloss.Color("#FF9999") //Red
|
||||||
|
c = lipgloss.Color("161") //Red
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
func (i TODO) Description() string {
|
func (i TODO) Description() string {
|
||||||
out,err := i.Props.Get(ical.PropDescription).Text()
|
out, err := i.Props.Get(ical.PropDescription).Text()
|
||||||
if err != nil {return ""}
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
func (i TODO) FilterValue() string {
|
func (i TODO) FilterValue() string {
|
||||||
out1,err1 := i.Props.Get(ical.PropSummary).Text()
|
// index
|
||||||
out2,err2 := i.Props.Get(ical.PropDescription).Text()
|
out1, err1 := i.Props.Get(ical.PropSummary).Text()
|
||||||
if err1 != nil && err2 != nil {return ""}
|
out2, err2 := i.Props.Get(ical.PropDescription).Text()
|
||||||
return out1+out2
|
if err1 != nil && err2 != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return out1 + out2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// type itemDelegate struct{}
|
||||||
|
//
|
||||||
|
// func (d itemDelegate) Height() int { return 1 }
|
||||||
|
// func (d itemDelegate) Spacing() int { return 0 }
|
||||||
|
// func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
||||||
|
// func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
||||||
|
// i, ok := listItem.(item)
|
||||||
|
// if !ok {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// str := fmt.Sprintf("%d. %s", index+1, i)
|
||||||
|
//
|
||||||
|
// fn := itemStyle.Render
|
||||||
|
// if index == m.Index() {
|
||||||
|
// fn = func(s string) string {
|
||||||
|
// return selectedItemStyle.Render("> " + s)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, fn(str))
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (m *model) GatherTodos() (err error) {
|
func (m *model) GatherTodos() (err error) {
|
||||||
//TODO more modular approach
|
//TODO more modular approach
|
||||||
m.CalObjects, err = GetTODOs(m.Creds.CalendarPath)
|
m.CalObjects, err = GetTODOs(m.Creds.CalendarPath)
|
||||||
if err != nil {return}
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
calendarObjects := m.CalObjects //TODO rm me
|
calendarObjects := m.CalObjects //TODO rm me
|
||||||
// var todayTodos []TODO
|
// var todayTodos []TODO
|
||||||
|
|
||||||
today := time.Now()
|
today := time.Now()
|
||||||
todayTodosBuf, err := ParseDueDateTODOs(calendarObjects, today)
|
todayTodosBuf, err := ParseDueDateTODOs(calendarObjects, today)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
tomorrow := time.Now().AddDate(0, 0, 1)
|
tomorrow := time.Now().AddDate(0, 0, 1)
|
||||||
tomorrowTodosBuf, err := ParseDueDateTODOs(calendarObjects, tomorrow)
|
tomorrowTodosBuf, err := ParseDueDateTODOs(calendarObjects, tomorrow)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
todayTodosBuf,err = SortTodos_Default(todayTodosBuf)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
tomorrowTodosBuf,err = SortTodos_Default(tomorrowTodosBuf)
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
//TODO fmt - we can combine things below
|
||||||
var todayTodos,tomorrowTodos []TODO
|
var todayTodos, tomorrowTodos []TODO
|
||||||
for _,event := range todayTodosBuf {
|
for _, event := range todayTodosBuf {
|
||||||
todayTodos = append(todayTodos,TODO(event))
|
todayTodos = append(todayTodos, TODO(event))
|
||||||
}
|
}
|
||||||
for _,event := range tomorrowTodosBuf {
|
for _, event := range tomorrowTodosBuf {
|
||||||
tomorrowTodos = append(tomorrowTodos,TODO(event))
|
tomorrowTodos = append(tomorrowTodos, TODO(event))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var itemsToday []list.Item
|
var itemsToday []list.Item
|
||||||
var itemsTomorrow []list.Item
|
var itemsTomorrow []list.Item
|
||||||
@ -73,32 +182,140 @@ func (m *model) GatherTodos() (err error) {
|
|||||||
itemsTomorrow = append(itemsTomorrow, todo)
|
itemsTomorrow = append(itemsTomorrow, todo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delegateKeys := newDelegateKeyMap()
|
||||||
|
|
||||||
delegateKeys := newDelegateKeyMap()
|
|
||||||
delegate := m.newItemDelegate(delegateKeys)
|
delegate := m.newItemDelegate(delegateKeys)
|
||||||
// m.TodayTab = list.New(itemsToday, list.NewDefaultDelegate(), 0, 0)
|
// m.TodayTab = list.New(itemsToday, list.NewDefaultDelegate(), 0, 0)
|
||||||
m.TodayTab = list.New(itemsToday, delegate, 0, 0)
|
m.TodayTab = list.New(itemsToday, delegate, 0, 0)
|
||||||
m.TodayTab.Title = "Today"
|
m.TodayTab.FilterInput.Reset() //TODO debug
|
||||||
|
// m.TodayTab.Title = "Today"
|
||||||
|
// m.TodayTab.SetShowStatusBar(false)
|
||||||
|
// m.TodayTab.SetFilteringEnabled(false)
|
||||||
|
// m.TodayTab.Styles.Title = titleStyle
|
||||||
|
// m.TodayTab.Styles.PaginationStyle = paginationStyle
|
||||||
|
// m.TodayTab.Styles.HelpStyle = helpStyle
|
||||||
|
|
||||||
// m.TomorrowTab = list.New(itemsTomorrow, list.NewDefaultDelegate(), 0, 0)
|
// m.TomorrowTab = list.New(itemsTomorrow, list.NewDefaultDelegate(), 0, 0)
|
||||||
m.TomorrowTab = list.New(itemsTomorrow, delegate, 0, 0)
|
m.TomorrowTab = list.New(itemsTomorrow, delegate, 0, 0)
|
||||||
|
m.TodayTab.FilterInput.Reset()//TODO debug
|
||||||
m.TomorrowTab.Title = "Tomorrow"
|
m.TomorrowTab.Title = "Tomorrow"
|
||||||
|
// m.TomorrowTab.SetShowStatusBar(false)
|
||||||
|
// m.TomorrowTab.SetFilteringEnabled(false)
|
||||||
|
// m.TomorrowTab.Styles.Title = titleStyle
|
||||||
|
// m.TomorrowTab.Styles.PaginationStyle = paginationStyle
|
||||||
|
// m.TomorrowTab.Styles.HelpStyle = helpStyle
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//for sorting
|
||||||
|
type ByTime []ical.Event
|
||||||
|
func (a ByTime) Len() int { return len(a) }
|
||||||
|
func (a ByTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a ByTime) Less(i, j int) bool {
|
||||||
|
time1,_ := a[i].Props.DateTime("DUE",nil)
|
||||||
|
//TODO is there better way?
|
||||||
|
// if time1.Hour() == 0 || time1.Minute() == 0 {
|
||||||
|
// time1 = time.Date(time1.Year(), time1.Month(), time1.Day(), 23, 59, 0, 0, time.UTC)
|
||||||
|
// }
|
||||||
|
// timeInt1,_ := strconv.Atoi(fmt.Sprint("%d%d",time1.Hour(),time1.Minute()))
|
||||||
|
s1:=fmt.Sprintf("%d%d",time1.Hour(),time1.Minute())
|
||||||
|
time2,_ := a[j].Props.DateTime("DUE",nil)
|
||||||
|
// timeInt2,_ := strconv.Atoi(fmt.Sprint("%d%d",time2.Hour(),time2.Minute()))
|
||||||
|
s2 := fmt.Sprintf("%d%d",time2.Hour(),time2.Minute())
|
||||||
|
|
||||||
|
timeInt1, _ := strconv.Atoi(s1)
|
||||||
|
timeInt2, _ := strconv.Atoi(s2)
|
||||||
|
|
||||||
|
if timeInt1 == 0 {
|
||||||
|
timeInt1 = 9999
|
||||||
|
}
|
||||||
|
if timeInt2 == 0 {
|
||||||
|
timeInt2 = 9999
|
||||||
|
}
|
||||||
|
// if time2.Hour() == 0 || time2.Minute() == 0 {
|
||||||
|
// time2 = time.Date(time2.Year(), time2.Month(), time2.Day(), 23, 59, 0, 0, time.UTC)
|
||||||
|
// }
|
||||||
|
// return time2.After(time1)
|
||||||
|
return timeInt1<timeInt2
|
||||||
|
}
|
||||||
|
type ByPriority []ical.Event
|
||||||
|
func (a ByPriority) Len() int { return len(a) }
|
||||||
|
func (a ByPriority) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a ByPriority) Less(i, j int) bool {
|
||||||
|
prior1,err := a[i].Props.Get("PRIORITY").Int()
|
||||||
|
|
||||||
|
// in case we have `PRIORITY;VALUE=TEXT:0`
|
||||||
|
if err != nil {
|
||||||
|
prioStr, err := a[i].Props.Get("PRIORITY").Text()
|
||||||
|
if err == nil {
|
||||||
|
prior1, err = strconv.Atoi(prioStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//if no priority
|
||||||
|
if prior1 == 0 {
|
||||||
|
prior1 = 11
|
||||||
|
}
|
||||||
|
|
||||||
|
prior2,_ := a[j].Props.Get("PRIORITY").Int()
|
||||||
|
if prior2 == 0 {
|
||||||
|
prior2 = 11
|
||||||
|
}
|
||||||
|
|
||||||
|
return prior1 < prior2
|
||||||
|
}
|
||||||
|
func SortTodos_Default(inputEvents []ical.Event) ([]ical.Event, error) {
|
||||||
|
//TODO sort other alphabetically
|
||||||
|
var timeEvents,priorityEvents,output []ical.Event
|
||||||
|
|
||||||
|
for _,event := range inputEvents {
|
||||||
|
time,err := event.Props.DateTime("DUE",nil)
|
||||||
|
if (err == nil) && (time.Hour() != 0 || time.Minute() != 0) {
|
||||||
|
timeEvents = append(timeEvents,event)
|
||||||
|
} else {
|
||||||
|
priorityEvents = append(priorityEvents, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(ByPriority(priorityEvents))
|
||||||
|
sort.Sort(ByTime(timeEvents))
|
||||||
|
|
||||||
|
output = timeEvents
|
||||||
|
output = append(output,priorityEvents...)
|
||||||
|
|
||||||
|
return output,nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (m *model) UpdateTodos(todo ical.Event) (err error) {
|
func (m *model) UpdateTodos(todo ical.Event) (err error) {
|
||||||
today := time.Now()
|
today := time.Now()
|
||||||
tomorrow := time.Now().AddDate(0, 0, 1)
|
tomorrow := time.Now().AddDate(0, 0, 1)
|
||||||
errorI := 0
|
errorI := 0
|
||||||
if strings.HasPrefix(todo.Props["DUE"][0].Value, today.Format("20060102")) {m.TodayTab.InsertItem(-1,TODO(todo))} else {errorI += 1}
|
if todo.Props["DUE"] != nil {
|
||||||
if strings.HasPrefix(todo.Props["DUE"][0].Value, tomorrow.Format("20060102")) {m.TomorrowTab.InsertItem(-1,TODO(todo)) } else {errorI += 1}
|
if strings.HasPrefix(todo.Props["DUE"][0].Value, today.Format("20060102")) {
|
||||||
|
m.TodayTab.InsertItem(-1, TODO(todo))
|
||||||
if errorI == 2 {return errors.New("don't match today and tomorrow")}
|
//TODO makey another type of "TodayTab"?, like []slice of tabs
|
||||||
|
//TODO sort after update
|
||||||
|
} else {
|
||||||
|
errorI += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(todo.Props["DUE"][0].Value, tomorrow.Format("20060102")) {
|
||||||
|
m.TomorrowTab.InsertItem(-1, TODO(todo))
|
||||||
|
//TODO sort after update
|
||||||
|
} else {
|
||||||
|
errorI += 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorI += 1
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
if errorI == 2 {
|
||||||
|
// return errors.New("don't match today and tomorrow") // debug???
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
185
tui.go
185
tui.go
@ -3,7 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
// "fmt"
|
// "fmt"
|
||||||
// "os"
|
// "os"
|
||||||
"time"
|
// "time"
|
||||||
// "io"
|
// "io"
|
||||||
// "strings"
|
// "strings"
|
||||||
|
|
||||||
@ -20,13 +20,10 @@ import (
|
|||||||
|
|
||||||
// "errors"
|
// "errors"
|
||||||
)
|
)
|
||||||
//TODO add new TODos
|
//TODO NEW FEATURE IMPORTANT - list today/tomorrow - edit TODOs
|
||||||
//TODO edit TODOs
|
//TODO NEW FEATURE - add search
|
||||||
//TODO add search
|
//TODO NEW FEATURE - add custom filter (search that saves with filter)
|
||||||
//TODO add custom filter (search that saves with filter)
|
// TODO NEW FEATURE - add tabs for days/searcf/filter?
|
||||||
// TODO add tabs for days/searcf/filter
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -34,8 +31,7 @@ import (
|
|||||||
type errMsg struct {message string}
|
type errMsg struct {message string}
|
||||||
|
|
||||||
|
|
||||||
//TODO fix multiple errHandlers
|
//TODO fix multiple errHandlers, rm m.errHandler, rename CLI errHandler (in main.go). rename errHandler_tui to errHandler. Rename ebery call
|
||||||
//TODO rm me
|
|
||||||
func (m model) errHandler(err error,desc string) (tea.Cmd) {
|
func (m model) errHandler(err error,desc string) (tea.Cmd) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := desc+": "+err.Error()
|
output := desc+": "+err.Error()
|
||||||
@ -71,32 +67,39 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
switch m.ActiveWindow {
|
switch m.ActiveWindow {
|
||||||
|
case "tomorrow": //TODO rm me debug
|
||||||
|
case "a":
|
||||||
|
m.ActiveWindow = "addTODO"
|
||||||
|
return m, nil
|
||||||
case "today": //TODO rm me debug
|
case "today": //TODO rm me debug
|
||||||
switch keypress := msg.String(); keypress {
|
switch keypress := msg.String(); keypress {
|
||||||
case "q", "ctrl+c":
|
case "q", "ctrl+c":
|
||||||
// m.quitting = true
|
// m.quitting = true
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
|
case "a":
|
||||||
case "y":
|
m.ActiveWindow = "addTODO"
|
||||||
// m."2797322749061742597"
|
|
||||||
case "h":
|
|
||||||
|
|
||||||
todoInfo := TodoInterface{
|
|
||||||
name: "testName1",
|
|
||||||
description: "description",
|
|
||||||
priority: 3,
|
|
||||||
dueTime: time.Now(),
|
|
||||||
alarmOffset: "1h",
|
|
||||||
}
|
|
||||||
task, err := CreateTodo(todoInfo)
|
|
||||||
if err != nil {return m, m.errHandler(err,"test fail")}
|
|
||||||
err = m.UploadTodo(task)
|
|
||||||
if err != nil {return m, m.errHandler(err,"test fail2")}
|
|
||||||
m.UpdateTodos(task)
|
|
||||||
// fmt.Println(&task)
|
|
||||||
// fmt.Println(err)
|
|
||||||
// time.Sleep(2*time.Second)
|
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
|
// case "y":
|
||||||
|
// m."2797322749061742597"
|
||||||
|
// case "h":
|
||||||
|
//
|
||||||
|
// todoInfo := TodoInterface{
|
||||||
|
// name: "testName1",
|
||||||
|
// description: "description",
|
||||||
|
// priority: 3,
|
||||||
|
// dueTime: time.Now(),
|
||||||
|
// alarmOffset: "1h",
|
||||||
|
// }
|
||||||
|
// task, err := CreateTodo(todoInfo)
|
||||||
|
// if err != nil {return m, m.errHandler(err,"test fail")}
|
||||||
|
// err = m.UploadTodo(task)
|
||||||
|
// if err != nil {return m, m.errHandler(err,"test fail2")}
|
||||||
|
// m.UpdateTodos(task)
|
||||||
|
// // fmt.Println(&task)
|
||||||
|
// // fmt.Println(err)
|
||||||
|
// // time.Sleep(2*time.Second)
|
||||||
|
// return m, nil
|
||||||
}
|
}
|
||||||
case "calendarChoose":
|
case "calendarChoose":
|
||||||
switch keypress := msg.String(); keypress {
|
switch keypress := msg.String(); keypress {
|
||||||
@ -124,41 +127,108 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
case "login":
|
case "login":
|
||||||
switch keypress := msg.String(); keypress {
|
switch keypress := msg.String(); keypress {
|
||||||
case "ctrl+c", "q":
|
// case "ctrl+c", "q"://are you stupid?
|
||||||
return m, tea.Quit
|
// return m, tea.Quit
|
||||||
case "enter":
|
case "enter":
|
||||||
if m.focused == len(m.loginInputs)-1 {
|
if m.focused == len(m.loginInputs)-1 {
|
||||||
//TODO check that we have all fields not empty and notificate about it
|
//TODO check that we have all fields not empty and notificate about it
|
||||||
//TODO submit
|
|
||||||
for i := range m.loginInputs {
|
for i := range m.loginInputs {
|
||||||
m.loginInputs[i], cmd = m.loginInputs[i].Update(msg)
|
m.loginInputs[i], cmd = m.loginInputs[i].Update(msg)
|
||||||
}
|
}
|
||||||
// fmt.Println(m.loginInputs[url].Value())//DEBUG
|
|
||||||
m.Creds.URL = m.loginInputs[url].Value()
|
m.Creds.URL = m.loginInputs[url].Value()
|
||||||
m.Creds.Username = m.loginInputs[login].Value()
|
m.Creds.Username = m.loginInputs[login].Value()
|
||||||
m.Creds.Password = m.loginInputs[pass].Value()
|
m.Creds.Password = m.loginInputs[pass].Value()
|
||||||
// fmt.Println(m.Creds.URL)//DEBUG
|
|
||||||
|
|
||||||
// time.Sleep(1 * time.Second) ///DEBUG
|
|
||||||
// m.Creds.
|
|
||||||
// return m, nil
|
|
||||||
err := m.LoginToCalendar()
|
err := m.LoginToCalendar()
|
||||||
if err != nil {return m, m.errHandler(err,"Failed to authenticate")}
|
if err != nil {return m, m.errHandler(err,"Failed to authenticate")}
|
||||||
// try login -> choose calendar -> store -> move to getting stuff
|
// try login -> choose calendar -> store -> move to getting stuff
|
||||||
return m, nil
|
return m, nil
|
||||||
// return m, tea.Quit
|
// return m, tea.Quit
|
||||||
}
|
}
|
||||||
m.nextInput()
|
m.nextLoginInput()
|
||||||
case "shift+tab", "up":
|
case "shift+tab", "up":
|
||||||
m.prevInput()
|
// buttonStyle = lipgloss.NewStyle()
|
||||||
|
m.prevLoginInput()
|
||||||
case "tab", "down":
|
case "tab", "down":
|
||||||
m.nextInput()
|
// buttonStyle = buttonStyle.Background(lipgloss.Color("#7D56F4"))
|
||||||
|
m.nextLoginInput()
|
||||||
}
|
}
|
||||||
for i := range m.loginInputs {
|
for i := range m.loginInputs {
|
||||||
m.loginInputs[i].Blur()
|
m.loginInputs[i].Blur()
|
||||||
}
|
}
|
||||||
m.loginInputs[m.focused].Focus()
|
m.loginInputs[m.focused].Focus()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
case "addTODO":
|
||||||
|
switch keypress := msg.String(); keypress {
|
||||||
|
// case "ctrl+c", "q": //are you stupid?
|
||||||
|
// return m, tea.Quit
|
||||||
|
case "enter":
|
||||||
|
if m.focused == len(m.todoAddInputs)-1 {
|
||||||
|
//TODO check that we have all fields not empty or contains wrong value and notify about it (add textinput validator in tui-model.go)
|
||||||
|
|
||||||
|
for i := range m.todoAddInputs {
|
||||||
|
m.todoAddInputs[i], cmd = m.todoAddInputs[i].Update(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := m.AddTODOtoList()
|
||||||
|
if err != nil {return m, m.errHandler(err,"Failed to add TODO")}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
m.nextTODOInput()
|
||||||
|
m.addTimeFocus = false
|
||||||
|
// return m, nil
|
||||||
|
case "shift+tab", "up":
|
||||||
|
// buttonStyle = lipgloss.NewStyle()
|
||||||
|
m.addTimeFocus = false
|
||||||
|
m.prevTODOInput()
|
||||||
|
// return m, nil
|
||||||
|
case "right":
|
||||||
|
//TODO BUG - cant move left-right, probably just move return in IF statement
|
||||||
|
//TODO BUG - focus todoAddInputsTime only if cursor at last character
|
||||||
|
// if focus Due Time
|
||||||
|
if m.focused == 1 {
|
||||||
|
m.addTimeFocus = true
|
||||||
|
m.todoAddInputs[m.focused].Blur()
|
||||||
|
if m.todoAddInputsTime[1].Focused() {
|
||||||
|
m.todoAddInputsTime[1].Blur()
|
||||||
|
m.todoAddInputsTime[0].Focus()
|
||||||
|
} else {
|
||||||
|
m.todoAddInputsTime[0].Blur()
|
||||||
|
m.todoAddInputsTime[1].Focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
case "left":
|
||||||
|
//TODO BUG - cant move left-right, probably just move return in IF statement
|
||||||
|
//TODO BUG - focus todoAddInputs only if cursor at zero character
|
||||||
|
if m.focused == 1 {
|
||||||
|
m.todoAddInputs[m.focused].Blur()
|
||||||
|
if m.todoAddInputsTime[0].Focused() {
|
||||||
|
m.todoAddInputsTime[0].Blur()
|
||||||
|
m.todoAddInputsTime[1].Focus()
|
||||||
|
} else {
|
||||||
|
m.todoAddInputsTime[0].Blur()
|
||||||
|
m.todoAddInputsTime[1].Blur()
|
||||||
|
m.todoAddInputs[m.focused].Focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
case "tab", "down":
|
||||||
|
// buttonStyle = buttonStyle.Background(lipgloss.Color("#7D56F4"))
|
||||||
|
m.nextTODOInput()
|
||||||
|
m.addTimeFocus = false
|
||||||
|
// return m, nil
|
||||||
|
}
|
||||||
|
// for i := range m.todoAddInputs {
|
||||||
|
// m.todoAddInputs[i].Blur()
|
||||||
|
// }
|
||||||
|
// for i := range m.todoAddInputsTime {
|
||||||
|
// m.todoAddInputsTime[i].Blur()
|
||||||
|
// }
|
||||||
|
// if !m.addTimeFocus {m.todoAddInputs[m.focused].Focus()}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch keypress := msg.String(); keypress {
|
switch keypress := msg.String(); keypress {
|
||||||
@ -185,17 +255,19 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
// TODO add new element
|
// TODO add new element
|
||||||
// return m, tea.Quit
|
// return m, tea.Quit
|
||||||
// }
|
// }
|
||||||
case "t":
|
// case "t":
|
||||||
// TODO add new element
|
// // TODO add new element
|
||||||
return m, tea.Quit
|
// return m, tea.Quit
|
||||||
}
|
}
|
||||||
|
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
|
|
||||||
switch m.ActiveWindow {
|
switch m.ActiveWindow {
|
||||||
|
//TODO BUG - addTODO/login - output render breaks a bit if errorHandler appeared on screen
|
||||||
// case "login": //TODO
|
// case "login": //TODO
|
||||||
// m.TodayTab.SetSize(msg.Width-h, msg.Height-v)
|
// m.TodayTab.SetSize(msg.Width-h, msg.Height-v)
|
||||||
// case "calendarChoose": //TODO
|
// case "calendarChoose": //TODO
|
||||||
|
// case "addTODO": //TODO
|
||||||
// m.calendarList.SetWidth(msg.Width)
|
// m.calendarList.SetWidth(msg.Width)
|
||||||
case "today":
|
case "today":
|
||||||
h, v := docStyle.GetFrameSize()
|
h, v := docStyle.GetFrameSize()
|
||||||
@ -218,6 +290,16 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
for i := range m.loginInputs {
|
for i := range m.loginInputs {
|
||||||
m.loginInputs[i], cmd = m.loginInputs[i].Update(msg)
|
m.loginInputs[i], cmd = m.loginInputs[i].Update(msg)
|
||||||
}
|
}
|
||||||
|
case "addTODO":
|
||||||
|
if (m.focused == 1) && (!m.todoAddInputs[m.focused].Focused()) {
|
||||||
|
for i := range m.todoAddInputsTime {
|
||||||
|
m.todoAddInputsTime[i], cmd = m.todoAddInputsTime[i].Update(msg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := range m.todoAddInputs {
|
||||||
|
m.todoAddInputs[i], cmd = m.todoAddInputs[i].Update(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
case "calendarChoose":
|
case "calendarChoose":
|
||||||
m.calendarList, cmd = m.calendarList.Update(msg)
|
m.calendarList, cmd = m.calendarList.Update(msg)
|
||||||
case "today":
|
case "today":
|
||||||
@ -228,6 +310,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case "": //exit on key press
|
case "": //exit on key press
|
||||||
switch tmp := msg.(type) { //TODO debug and optimize
|
switch tmp := msg.(type) { //TODO debug and optimize
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
|
// TODO exit on key press
|
||||||
_ = tmp
|
_ = tmp
|
||||||
if m.LoggedIn {
|
if m.LoggedIn {
|
||||||
m.ActiveWindow = "today"
|
m.ActiveWindow = "today"
|
||||||
@ -257,6 +340,18 @@ func (m model) View() string {
|
|||||||
// MarginRight(width/3)
|
// MarginRight(width/3)
|
||||||
tabOutput = loginStyle.Render(m.RenderLogin())
|
tabOutput = loginStyle.Render(m.RenderLogin())
|
||||||
// w, h := lipgloss.Size(tabOutput)
|
// w, h := lipgloss.Size(tabOutput)
|
||||||
|
case "addTODO":
|
||||||
|
width, height, _ := term.GetSize(0)
|
||||||
|
width -= 2
|
||||||
|
height -= 2
|
||||||
|
loginStyle = loginStyle.
|
||||||
|
// Width(30).
|
||||||
|
// Height(height/5).
|
||||||
|
MarginTop(height / 5).
|
||||||
|
MarginLeft(width/2 - 20)
|
||||||
|
// MarginRight(width/3)
|
||||||
|
tabOutput = loginStyle.Render(m.RenderAddTodo())
|
||||||
|
// w, h := lipgloss.Size(tabOutput)
|
||||||
case "calendarChoose":
|
case "calendarChoose":
|
||||||
width, height, _ := term.GetSize(0)
|
width, height, _ := term.GetSize(0)
|
||||||
width -= 2
|
width -= 2
|
||||||
|
Loading…
Reference in New Issue
Block a user