v1!!! a lot of fixes and new addTODO feature!
This commit is contained in:
parent
43843c705d
commit
9946c4ffce
255
caldav.go
255
caldav.go
@ -3,15 +3,15 @@ package main
|
||||
import (
|
||||
"context"
|
||||
// "fmt"
|
||||
"github.com/emersion/go-ical"
|
||||
webdav "github.com/emersion/go-webdav"
|
||||
"github.com/emersion/go-webdav/caldav"
|
||||
"github.com/emersion/go-ical"
|
||||
"github.com/teambition/rrule-go"
|
||||
|
||||
"strings"
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
"errors"
|
||||
"github.com/google/uuid"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
// "net/url"
|
||||
@ -30,7 +30,7 @@ import (
|
||||
// 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 client *caldav.Client // clientCalDAV
|
||||
// var calendarObjects []caldav.CalendarObject
|
||||
@ -38,10 +38,10 @@ var ctx = context.Background()
|
||||
|
||||
// var authSession caldav.Client // clientCalDAV
|
||||
|
||||
func InitDAVclients(url,user,pass string) error {
|
||||
func InitDAVclients(url, user, pass string) error {
|
||||
|
||||
var err error
|
||||
authSession := webdav.HTTPClientWithBasicAuth(nil, user,pass)
|
||||
authSession := webdav.HTTPClientWithBasicAuth(nil, user, pass)
|
||||
|
||||
clientWebDAV, err = webdav.NewClient(authSession, url)
|
||||
if err != nil {
|
||||
@ -58,7 +58,7 @@ func InitDAVclients(url,user,pass string) 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 {
|
||||
// Handle error
|
||||
return err
|
||||
@ -100,7 +100,7 @@ func GetCalendars() ([]caldav.Calendar, 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()
|
||||
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
|
||||
@ -234,31 +234,6 @@ func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err
|
||||
// return output, nil
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.Event, error) {
|
||||
var output []ical.Event
|
||||
|
||||
@ -278,7 +253,7 @@ func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.
|
||||
// // }
|
||||
// }
|
||||
|
||||
for _, eventComponent:= range (*calObj.Data).Children {
|
||||
for _, eventComponent := range (*calObj.Data).Children {
|
||||
event := ical.Event{Component: eventComponent}
|
||||
// for _, event := range (*calObj.Data).Children {
|
||||
// fmt.Println("1:", event) //TODO rm me
|
||||
@ -318,18 +293,19 @@ func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.
|
||||
if event.Props["DESCRIPTION"] == nil {
|
||||
tmpTODO.Props.SetText(ical.PropDescription, "")
|
||||
}
|
||||
if event.Props["PRIORITY"] == nil {
|
||||
tmpTODO.Props.SetText("PRIORITY", "0")
|
||||
}
|
||||
|
||||
|
||||
// var todoTime string
|
||||
// due := (*event).Props["DUE"][0].Value
|
||||
// index := strings.Index(due, "T")
|
||||
// if index != -1 {
|
||||
// str := due[index+1:]
|
||||
// todoTime = str[:2] + ":" + str[2:4]
|
||||
//
|
||||
// tmpTODO.Time = todoTime
|
||||
// }
|
||||
|
||||
// var todoTime string
|
||||
// due := (*event).Props["DUE"][0].Value
|
||||
// index := strings.Index(due, "T")
|
||||
// if index != -1 {
|
||||
// str := due[index+1:]
|
||||
// todoTime = str[:2] + ":" + str[2:4]
|
||||
//
|
||||
// tmpTODO.Time = todoTime
|
||||
// }
|
||||
|
||||
output = append(output, tmpTODO)
|
||||
}
|
||||
@ -343,8 +319,8 @@ func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.
|
||||
// //TODO UID:7ed30f40-fce1-422c-be3b-0486dcfe8943
|
||||
// //TODO RELATED-TO:7ed30f40-fce1-422c-be3b-0486dcfe8943 # subtask
|
||||
// //TODO PRIORITY:1 #1-high, 5-mid, 9-low
|
||||
//TODO on complete -repeat function
|
||||
//TODO repeate - RRULE:FREQ=DAILY;INTERVAL=1
|
||||
//TODO on complete -repeat function
|
||||
//TODO repeate - RRULE:FREQ=DAILY;INTERVAL=1
|
||||
// RRULE:FREQ=WEEKLY;INTERVAL=1
|
||||
|
||||
//TODO if no repeat - mark as complted
|
||||
@ -367,18 +343,6 @@ func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]ical.
|
||||
return output, nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
type TodoInterface struct {
|
||||
name string
|
||||
description string
|
||||
@ -390,17 +354,27 @@ type TodoInterface struct {
|
||||
//TODO subtasks
|
||||
}
|
||||
|
||||
|
||||
func CreateTodo(info TodoInterface) (event ical.Event,err error) {
|
||||
func CreateTodo(info TodoInterface) (event ical.Event, err error) {
|
||||
uid, err := uuid.NewUUID()
|
||||
if err != nil {return}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
event = *ical.NewEvent()
|
||||
event.Name = ical.CompToDo //VTODO
|
||||
event.Props.SetText(ical.PropUID, uid.String())
|
||||
event.Props.SetText(ical.PropSummary, info.name)
|
||||
event.Props.SetText(ical.PropDescription, info.description)
|
||||
if !info.dueTime.IsZero() {event.Props.SetDateTime(ical.PropDue, info.dueTime)} // 'zero' time is `time.Time{}`
|
||||
//TODO add alarm
|
||||
if !info.dueTime.IsZero() {
|
||||
hour := info.dueTime.Hour()
|
||||
minute := info.dueTime.Minute()
|
||||
if (hour != 0 || minute != 0 ) {
|
||||
event.Props.SetDateTime(ical.PropDue, info.dueTime)
|
||||
} else {
|
||||
event.Props.SetDate(ical.PropDue, info.dueTime)
|
||||
}
|
||||
// }
|
||||
} // 'zero' time is `time.Time{}`
|
||||
|
||||
switch info.priority {
|
||||
case 0: //No priority
|
||||
event.Props.SetText(ical.PropPriority, "0")
|
||||
@ -420,11 +394,10 @@ func CreateTodo(info TodoInterface) (event ical.Event,err error) {
|
||||
// alarmComponent := ical.Component{Name:ical.CompAlarm}
|
||||
alarmComponent := ical.NewComponent(ical.CompAlarm)
|
||||
|
||||
|
||||
alarmComponent.Props.SetText(ical.PropAction,"DISPLAY")
|
||||
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")
|
||||
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
|
||||
@ -434,29 +407,28 @@ func CreateTodo(info TodoInterface) (event ical.Event,err error) {
|
||||
|
||||
}
|
||||
|
||||
if strings.HasSuffix(info.alarmOffset,"h") {
|
||||
offset,_ := strings.CutSuffix(info.alarmOffset,"h")
|
||||
value="-PT" + offset + "H"
|
||||
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"
|
||||
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"
|
||||
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)
|
||||
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())
|
||||
@ -464,8 +436,6 @@ func CreateTodo(info TodoInterface) (event ical.Event,err error) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (m model) UploadTodo(event ical.Event) (err error) {
|
||||
// event.Props.SetDateTime(ical.PropDateTimeStart, startDateTime)
|
||||
|
||||
@ -482,25 +452,30 @@ func (m model) UploadTodo(event ical.Event) (err error) {
|
||||
calendar.Props.SetText(ical.PropVersion, "2.0")
|
||||
calendar.Component.Children = append(calendar.Component.Children, event.Component)
|
||||
|
||||
todoGUID,err := event.Props.Get(ical.PropUID).Text()
|
||||
if err != nil {return err}
|
||||
todoGUID, err := event.Props.Get(ical.PropUID).Text()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//TODO check GUID uniq and regenerate if needed
|
||||
|
||||
var buf strings.Builder
|
||||
encoder := ical.NewEncoder(&buf)
|
||||
err = encoder.Encode(calendar)
|
||||
if err != nil {return err}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.PutCalendarObject(ctx, m.Creds.CalendarPath+todoGUID+".isc", calendar)
|
||||
if err != nil {return err}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
|
||||
// func (m model) DelTodo(delUID string) (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}
|
||||
|
||||
// calendar, err := client.GetCalendarObject(ctx, m.Creds.CalendarPath+delUID+".isc")
|
||||
@ -536,15 +511,19 @@ func (m model) DelTodo(todo ical.Event) (err error) {
|
||||
client := &http.Client{}
|
||||
|
||||
parts := strings.Split(m.Creds.URL, "/")
|
||||
baseURL := parts[0]+"//"+parts[2]
|
||||
baseURL := parts[0] + "//" + parts[2]
|
||||
// Create request
|
||||
req, err := http.NewRequest("DELETE", baseURL + m.Creds.CalendarPath + delUID + ".isc", nil)
|
||||
if err != nil {return}
|
||||
req, err := http.NewRequest("DELETE", baseURL+m.Creds.CalendarPath+delUID+".isc", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.SetBasicAuth(m.Creds.Username, m.Creds.Password)
|
||||
|
||||
// Fetch Request
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {return}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// resp.Body.Close()
|
||||
defer resp.Body.Close()
|
||||
|
||||
@ -555,37 +534,53 @@ func (m model) DelTodo(todo ical.Event) (err error) {
|
||||
// return
|
||||
// }
|
||||
|
||||
if resp.Status == "204 No Content" {return nil}
|
||||
if resp.Status == "204 No Content" {
|
||||
return nil
|
||||
}
|
||||
if resp.Status == "404 Not Found" {
|
||||
// Nextcloud Tasks create differend GUID for Vtodo and path
|
||||
// TODO code fmt - Highway to for-if hell
|
||||
|
||||
for _, calObj := range m.CalObjects {
|
||||
for _, eventComponent:= range (*calObj.Data).Children {
|
||||
for _, eventComponent := range (*calObj.Data).Children {
|
||||
event := ical.Event{Component: eventComponent}
|
||||
if event.Name == "VTODO" {
|
||||
// eventUID,_ := (*event).Props["UID"][0].Text()
|
||||
|
||||
eventUID,err := event.Props.Get(ical.PropUID).Text()
|
||||
eventUID, err := event.Props.Get(ical.PropUID).Text()
|
||||
|
||||
if err != nil {eventUID = ""}
|
||||
if (eventUID == delUID) {
|
||||
if err != nil {
|
||||
eventUID = ""
|
||||
}
|
||||
if eventUID == delUID {
|
||||
//verify that it's indeed that event by comparing Name
|
||||
sum1,err:=todo.Props.Get("SUMMARY").Text()
|
||||
if err != nil {return err}
|
||||
sum2,err:=event.Props.Get("SUMMARY").Text()
|
||||
if err != nil {return err}
|
||||
if sum1==sum2 {
|
||||
req2, err := http.NewRequest("DELETE", baseURL + calObj.Path, nil)
|
||||
if err != nil {return err}
|
||||
sum1, err := todo.Props.Get("SUMMARY").Text()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sum2, err := event.Props.Get("SUMMARY").Text()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sum1 == sum2 {
|
||||
req2, err := http.NewRequest("DELETE", baseURL+calObj.Path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req2.SetBasicAuth(m.Creds.Username, m.Creds.Password)
|
||||
|
||||
// Fetch Request
|
||||
resp2, err := client.Do(req2)
|
||||
if err != nil {return err}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp2.Body.Close()
|
||||
if resp2.Status == "204 No Content" {return nil} else {return errors.New("Can't delete, response status: "+resp2.Status+".")}
|
||||
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")
|
||||
@ -598,24 +593,25 @@ func (m model) DelTodo(todo ical.Event) (err error) {
|
||||
|
||||
// return errors.New("test")
|
||||
}
|
||||
time.Sleep(2*time.Second) //TODO DEBUG RM ME
|
||||
time.Sleep(2 * time.Second) //TODO DEBUG RM ME
|
||||
// Display Results
|
||||
// fmt.Println("response Status : ", resp.Status)
|
||||
// fmt.Println("response Headers : ", resp.Header)
|
||||
// fmt.Println("response Body : ", string(respBody))
|
||||
|
||||
// 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) {
|
||||
//TODO is there proper edit function ???
|
||||
// uid,err := todo.Props.Get(ical.PropUID).Text()
|
||||
// if err != nil {return}
|
||||
|
||||
err = m.DelTodo(todo)
|
||||
if err != nil {return}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = m.UploadTodo(todo)
|
||||
if err != nil {
|
||||
@ -626,44 +622,51 @@ func (m model) EditTodo(todo ical.Event) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
//TODO
|
||||
// TODO
|
||||
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 {
|
||||
var rOptions *rrule.ROption
|
||||
rOptions, err = todo.Props.RecurrenceRule()
|
||||
if err != nil {return}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
offset := rOptions.Interval
|
||||
var currentDUE time.Time
|
||||
currentDUE,err = todo.Props.DateTime("DUE",nil)
|
||||
if err != nil {return}
|
||||
currentDUE, err = todo.Props.DateTime("DUE", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch rOptions.Freq {
|
||||
case rrule.DAILY:
|
||||
currentDUE = currentDUE.AddDate(0,0,offset)
|
||||
currentDUE = currentDUE.AddDate(0, 0, offset)
|
||||
case rrule.WEEKLY:
|
||||
currentDUE = currentDUE.AddDate(0,0,7*offset)
|
||||
currentDUE = currentDUE.AddDate(0, 0, 7*offset)
|
||||
case rrule.MONTHLY:
|
||||
currentDUE = currentDUE.AddDate(0,offset,0)
|
||||
currentDUE = currentDUE.AddDate(0, offset, 0)
|
||||
case rrule.YEARLY:
|
||||
currentDUE = currentDUE.AddDate(offset,0,0)
|
||||
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)
|
||||
|
||||
todo.Props.SetDateTime("DUE", currentDUE)
|
||||
} else {
|
||||
todo.Props.SetText(ical.PropStatus,"COMPLETED")
|
||||
todo.Props.SetText(ical.PropPercentComplete,"100")
|
||||
todo.Props.SetDateTime(ical.PropCompleted,time.Now())
|
||||
todo.Props.SetText(ical.PropStatus, "COMPLETED")
|
||||
todo.Props.SetText(ical.PropPercentComplete, "100")
|
||||
todo.Props.SetDateTime(ical.PropCompleted, time.Now())
|
||||
}
|
||||
|
||||
err = m.EditTodo(todo)
|
||||
if err != nil {return}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
17
keyring.go
17
keyring.go
@ -95,16 +95,17 @@ func getCredentialsFromKeyring_wrapper() (url, login, password, calendar string,
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func getCredentialsFromKeyring() (Credentials,error) {
|
||||
func getCredentialsFromKeyring() (Credentials, error) {
|
||||
//TODO inconsistent approach compared to caldav.go
|
||||
url,username,password,calendar,err := getCredentialsFromKeyring_wrapper()
|
||||
if err != nil {return Credentials{},err}
|
||||
url, username, password, calendar, err := getCredentialsFromKeyring_wrapper()
|
||||
if err != nil {
|
||||
return Credentials{}, err
|
||||
}
|
||||
return Credentials{
|
||||
URL:url,
|
||||
Username:username,
|
||||
Password:password,
|
||||
CalendarPath:calendar,
|
||||
URL: url,
|
||||
Username: username,
|
||||
Password: password,
|
||||
CalendarPath: calendar,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
30
main.go
30
main.go
@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"net/http"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
// "sync"
|
||||
// "time"
|
||||
|
||||
@ -13,12 +13,16 @@ import (
|
||||
|
||||
"github.com/emersion/go-webdav/caldav"
|
||||
// "slices"
|
||||
|
||||
|
||||
// "strconv"
|
||||
// "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
|
||||
|
||||
func errHandler(err error, message string) {
|
||||
@ -49,7 +53,7 @@ func main() {
|
||||
calendars, err = GetCalendars()
|
||||
errHandler(err, "Error getting calendars (incorrect url/login/password)")
|
||||
|
||||
//TODO crushs START
|
||||
//TODO BUG crush START
|
||||
var found bool
|
||||
// var calPath string
|
||||
if options.Calendar == "" {
|
||||
@ -57,29 +61,26 @@ func main() {
|
||||
// m.ActiveWindow = "login"
|
||||
}
|
||||
if options.Calendar != "" {
|
||||
for _,calendar := range calendars {
|
||||
for _, calendar := range calendars {
|
||||
if calendar.Name == options.Calendar {
|
||||
found = true
|
||||
// calPath = calendar.Path
|
||||
m.Creds.CalendarPath = calendar.Path
|
||||
}
|
||||
}
|
||||
if ! found {
|
||||
if !found {
|
||||
fmt.Println("we don't have calendar ", options.Calendar, ". We have:")
|
||||
for _,calendar := range calendars {
|
||||
for _, calendar := range calendars {
|
||||
fmt.Println(calendar.Name)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// fmt.Println(m)
|
||||
m.LoginToCalendar()
|
||||
m.CalendarToTodo()
|
||||
|
||||
|
||||
//TODO crushs End
|
||||
//TODO BUG crush End
|
||||
}
|
||||
} else {
|
||||
//TODO I'm on a highway to (IfElse) hell!
|
||||
@ -111,12 +112,12 @@ func main() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
//DEBUG stuff
|
||||
// task, err := CreateTodo("testName","description",3,time.Now())
|
||||
// errHandler(err,"test fail")
|
||||
// err = m.UploadTodo(task)
|
||||
// errHandler(err,"test fail2")
|
||||
// m.ActiveWindow = "addTODO"
|
||||
//DEBUG stuff
|
||||
|
||||
//TODO if task have alarm - make a notification / play sound...
|
||||
@ -128,5 +129,4 @@ func main() {
|
||||
|
||||
// fmt.Println(m.)
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,15 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"errors"
|
||||
"github.com/projectdiscovery/goflags"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var onceOptions sync.Once
|
||||
var options = &Options{}
|
||||
|
||||
//TODO NEW FEATURE - batch/simple add tasks via CLI
|
||||
|
||||
type Options struct {
|
||||
URL string
|
||||
// Threads int
|
||||
@ -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
|
||||
}
|
||||
|
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 (
|
||||
"fmt"
|
||||
// "github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"io"
|
||||
"strings"
|
||||
// "github.com/charmbracelet/lipgloss"
|
||||
@ -20,7 +20,6 @@ var ( //Calendars choose
|
||||
quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
|
||||
)
|
||||
|
||||
|
||||
func (m model) RenderCalendarChooser() string {
|
||||
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))
|
||||
}
|
||||
|
||||
|
||||
func (m *model) CalendarToTodo() (err error) {
|
||||
|
||||
m.LoggedIn = true
|
||||
m.GatherTodos()
|
||||
m.ActiveWindow = "today"
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
70
tui-login.go
70
tui-login.go
@ -8,18 +8,19 @@ import (
|
||||
|
||||
var docStyle = lipgloss.NewStyle().Margin(1, 2)
|
||||
|
||||
|
||||
var loginStyle = lipgloss.NewStyle().Width(40).Align(lipgloss.Center).BorderStyle(lipgloss.NormalBorder())
|
||||
|
||||
var inputStyle = lipgloss.NewStyle()
|
||||
// var buttonStyle = lipgloss.NewStyle().Background(lipgloss.Color("#7D56F4"))
|
||||
var buttonStyle = lipgloss.NewStyle()
|
||||
|
||||
const (
|
||||
url = iota
|
||||
login
|
||||
pass
|
||||
// btnFocus
|
||||
)
|
||||
|
||||
|
||||
func (m model) RenderLogin() string {
|
||||
|
||||
return fmt.Sprintf(
|
||||
@ -46,38 +47,86 @@ func (m model) RenderLogin() string {
|
||||
m.loginInputs[login].View(),
|
||||
inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("Password"),
|
||||
m.loginInputs[pass].View(), //TODO hide
|
||||
inputStyle.Render("Continue ->"),
|
||||
buttonStyle.Render("Continue ->"),
|
||||
)
|
||||
// .Align(lipgloss.Center).BorderStyle(lipgloss.NormalBorder())
|
||||
|
||||
}
|
||||
|
||||
func (m *model) nextInput() {
|
||||
func (m *model) nextLoginInput() {
|
||||
|
||||
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
|
||||
func (m *model) prevInput() {
|
||||
func (m *model) prevLoginInput() {
|
||||
|
||||
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) {
|
||||
err = m.InitDAVclients()
|
||||
if err != nil { return }
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.Calendars, err = GetCalendars()
|
||||
if err != nil { return }
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
items := []list.Item{}
|
||||
|
||||
for _,calendar := range m.Calendars {
|
||||
for _, calendar := range m.Calendars {
|
||||
// fmt.Println(calendar.Name)
|
||||
items = append(items,item(calendar.Name))
|
||||
items = append(items, item(calendar.Name))
|
||||
}
|
||||
|
||||
// m.LoggedIn = true TODO after calendar choose
|
||||
@ -95,6 +144,7 @@ func (m *model) LoginToCalendar() (err error) {
|
||||
m.calendarList = l
|
||||
m.ActiveWindow = "calendarChoose"
|
||||
|
||||
m.btnFocus = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
117
tui-model.go
117
tui-model.go
@ -3,13 +3,10 @@ package main
|
||||
import (
|
||||
// "fmt"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/emersion/go-webdav/caldav"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
|
||||
"github.com/emersion/go-webdav/caldav"
|
||||
)
|
||||
|
||||
|
||||
|
||||
type model struct {
|
||||
Tabs []string
|
||||
// TabContent []string
|
||||
@ -22,8 +19,12 @@ type model struct {
|
||||
calendarList list.Model
|
||||
calendarChoice string
|
||||
|
||||
todoAddInputs []textinput.Model
|
||||
todoAddInputsTime []textinput.Model
|
||||
addTimeFocus bool
|
||||
loginInputs []textinput.Model
|
||||
focused int
|
||||
btnFocus bool
|
||||
err error
|
||||
|
||||
Creds Credentials
|
||||
@ -41,46 +42,104 @@ type Credentials struct {
|
||||
CalendarPath string
|
||||
}
|
||||
|
||||
|
||||
func (m *model) CredentialsSave() (err error) {
|
||||
|
||||
//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
|
||||
if err != nil {return}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func InitModel() model {
|
||||
var inputs []textinput.Model = make([]textinput.Model, 3)
|
||||
inputs[url] = textinput.New()
|
||||
inputs[url].Placeholder = "https://nextcloud.example/remote.php/dav"
|
||||
inputs[url].Focus()
|
||||
// inputs[url].CharLimit = 20
|
||||
inputs[url].Width = 30
|
||||
inputs[url].Prompt = ""
|
||||
// inputs[url].Validate = urlValidator
|
||||
var loginInputs1 []textinput.Model = make([]textinput.Model, 3)
|
||||
loginInputs1[url] = textinput.New()
|
||||
loginInputs1[url].Placeholder = "https://nextcloud.example/remote.php/dav"
|
||||
loginInputs1[url].Focus()
|
||||
loginInputs1[url].Width = 30
|
||||
loginInputs1[url].Prompt = ""
|
||||
// loginInputs1[url].Validate = urlValidator //TODO
|
||||
|
||||
inputs[login] = textinput.New()
|
||||
inputs[login].Placeholder = "username"
|
||||
// inputs[login].CharLimit = 5
|
||||
inputs[login].Width = 30
|
||||
inputs[login].Prompt = ""
|
||||
// inputs[login].Validate = loginValidator
|
||||
loginInputs1[login] = textinput.New()
|
||||
loginInputs1[login].Placeholder = "username"
|
||||
loginInputs1[login].Width = 30
|
||||
loginInputs1[login].Prompt = ""
|
||||
|
||||
inputs[pass] = textinput.New() //TODO make pass hidden with "github.com/erikgeiser/promptkit/textinput"
|
||||
inputs[pass].Placeholder = "MySecurePassword"
|
||||
// inputs[pass].CharLimit = 3
|
||||
inputs[pass].Width = 30
|
||||
inputs[pass].Prompt = ""
|
||||
// inputs[pass].Validate = passValidator
|
||||
loginInputs1[pass] = textinput.New()
|
||||
loginInputs1[pass].Placeholder = "MySecurePassword"
|
||||
loginInputs1[pass].Width = 30
|
||||
loginInputs1[pass].Prompt = ""
|
||||
loginInputs1[pass].EchoMode = 1 //list.EchoPassword
|
||||
|
||||
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{
|
||||
Tabs: []string{"Today", "Tomorrow", "Add"},
|
||||
loginInputs: inputs,
|
||||
loginInputs: loginInputs1,
|
||||
focused: 0,
|
||||
err: nil,
|
||||
todoAddInputs: addTODOinputs,
|
||||
todoAddInputsTime: addTODOinputsTime,
|
||||
// Creds: Credentials{"test","test","test","test","test"},
|
||||
// TabContent: []string{"ERROR?", "Mascara Tab", "Foundation Tab"},
|
||||
}
|
||||
|
230
tui-todo-keys.go
230
tui-todo-keys.go
@ -1,17 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
"github.com/emersion/go-ical"
|
||||
// "time"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
// "fmt"
|
||||
// "io"
|
||||
// "strings"
|
||||
// "errors"
|
||||
// "golang.org/x/term"
|
||||
// "github.com/muesli/reflow/truncate"
|
||||
// "strings"
|
||||
)
|
||||
|
||||
// const (
|
||||
// bullet = "•"
|
||||
// ellipsis = "…"
|
||||
// )
|
||||
|
||||
|
||||
var (
|
||||
appStyle = lipgloss.NewStyle().Padding(1, 2)
|
||||
|
||||
@ -25,10 +35,149 @@ var (
|
||||
Render
|
||||
)
|
||||
|
||||
// buttons
|
||||
func (m model) newItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {
|
||||
d := list.NewDefaultDelegate()
|
||||
// type DefaultDelegate struct {
|
||||
// ShowDescription bool
|
||||
// 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 {
|
||||
var title string
|
||||
// var todoUID string
|
||||
@ -47,30 +196,56 @@ func (m model) newItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {
|
||||
switch {
|
||||
case key.Matches(msg, keys.choose):
|
||||
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()
|
||||
ml.RemoveItem(index)
|
||||
if len(ml.Items()) == 0 {
|
||||
keys.choose.SetEnabled(false)
|
||||
}
|
||||
return ml.NewStatusMessage(statusMessageStyle("You chose " + title))
|
||||
return ml.NewStatusMessage(statusMessageStyle("You completed " + title+"!"))
|
||||
|
||||
case key.Matches(msg, keys.remove):
|
||||
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()
|
||||
ml.RemoveItem(index)
|
||||
if len(ml.Items()) == 0 {
|
||||
keys.remove.SetEnabled(false)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
help := []key.Binding{keys.choose, keys.remove}
|
||||
help := []key.Binding{keys.choose, keys.remove, keys.add}
|
||||
|
||||
d.ShortHelpFunc = func() []key.Binding {
|
||||
return help
|
||||
@ -80,25 +255,52 @@ func (m model) newItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
type delegateKeyMap struct {
|
||||
choose key.Binding
|
||||
remove key.Binding
|
||||
add key.Binding
|
||||
}
|
||||
|
||||
|
||||
func newDelegateKeyMap() *delegateKeyMap {
|
||||
return &delegateKeyMap{
|
||||
choose: key.NewBinding(
|
||||
key.WithKeys("enter"),
|
||||
key.WithHelp("enter", "choose"),
|
||||
key.WithHelp("enter", "complete"),
|
||||
),
|
||||
remove: key.NewBinding(
|
||||
key.WithKeys("x", "backspace"),
|
||||
key.WithHelp("x", "delete"),
|
||||
key.WithKeys("backspace", "backspace"),
|
||||
key.WithHelp("backspace", "delete"),
|
||||
),
|
||||
add: key.NewBinding(
|
||||
key.WithKeys("a"),
|
||||
key.WithHelp("a", "Add TODO"),
|
||||
),
|
||||
//TODO PRIORITY FEATURE - edit TODO button
|
||||
}
|
||||
}
|
||||
|
285
tui-todo.go
285
tui-todo.go
@ -1,69 +1,178 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
"github.com/emersion/go-ical"
|
||||
"time"
|
||||
// "errors"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/emersion/go-ical"
|
||||
"strings"
|
||||
"errors"
|
||||
"time"
|
||||
"sort"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
|
||||
type TODO ical.Event
|
||||
|
||||
func (i TODO) Title() string {
|
||||
out,err := i.Props.Get(ical.PropSummary).Text()
|
||||
if err != nil {return "<EMPTY>"}
|
||||
return out
|
||||
|
||||
|
||||
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>"}
|
||||
out, err := i.Props.Get(ical.PropUID).Text()
|
||||
if err != nil {
|
||||
return "<EMPTY>"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (i TODO) PriorityColor() lipgloss.Color {
|
||||
clr, err := i.Props.Get("PRIORITY").Int()
|
||||
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 {
|
||||
out,err := i.Props.Get(ical.PropDescription).Text()
|
||||
if err != nil {return ""}
|
||||
out, err := i.Props.Get(ical.PropDescription).Text()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return out
|
||||
}
|
||||
func (i TODO) FilterValue() string {
|
||||
out1,err1 := i.Props.Get(ical.PropSummary).Text()
|
||||
out2,err2 := i.Props.Get(ical.PropDescription).Text()
|
||||
if err1 != nil && err2 != nil {return ""}
|
||||
return out1+out2
|
||||
// index
|
||||
out1, err1 := i.Props.Get(ical.PropSummary).Text()
|
||||
out2, err2 := i.Props.Get(ical.PropDescription).Text()
|
||||
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) {
|
||||
//TODO more modular approach
|
||||
//TODO more modular approach
|
||||
m.CalObjects, err = GetTODOs(m.Creds.CalendarPath)
|
||||
if err != nil {return}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
calendarObjects := m.CalObjects //TODO rm me
|
||||
// var todayTodos []TODO
|
||||
|
||||
today := time.Now()
|
||||
todayTodosBuf, err := ParseDueDateTODOs(calendarObjects, today)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
tomorrow := time.Now().AddDate(0, 0, 1)
|
||||
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
|
||||
// }
|
||||
|
||||
|
||||
var todayTodos,tomorrowTodos []TODO
|
||||
for _,event := range todayTodosBuf {
|
||||
todayTodos = append(todayTodos,TODO(event))
|
||||
//TODO fmt - we can combine things below
|
||||
var todayTodos, tomorrowTodos []TODO
|
||||
for _, event := range todayTodosBuf {
|
||||
todayTodos = append(todayTodos, TODO(event))
|
||||
}
|
||||
for _,event := range tomorrowTodosBuf {
|
||||
tomorrowTodos = append(tomorrowTodos,TODO(event))
|
||||
for _, event := range tomorrowTodosBuf {
|
||||
tomorrowTodos = append(tomorrowTodos, TODO(event))
|
||||
}
|
||||
|
||||
|
||||
var itemsToday []list.Item
|
||||
var itemsTomorrow []list.Item
|
||||
for _, todo := range todayTodos {
|
||||
@ -73,32 +182,140 @@ func (m *model) GatherTodos() (err error) {
|
||||
itemsTomorrow = append(itemsTomorrow, todo)
|
||||
}
|
||||
|
||||
|
||||
|
||||
delegateKeys := newDelegateKeyMap()
|
||||
delegate := m.newItemDelegate(delegateKeys)
|
||||
// m.TodayTab = list.New(itemsToday, list.NewDefaultDelegate(), 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, delegate, 0, 0)
|
||||
m.TodayTab.FilterInput.Reset()//TODO debug
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
//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) {
|
||||
today := time.Now()
|
||||
tomorrow := time.Now().AddDate(0, 0, 1)
|
||||
errorI := 0
|
||||
if strings.HasPrefix(todo.Props["DUE"][0].Value, today.Format("20060102")) {m.TodayTab.InsertItem(-1,TODO(todo))} else {errorI += 1}
|
||||
if strings.HasPrefix(todo.Props["DUE"][0].Value, tomorrow.Format("20060102")) {m.TomorrowTab.InsertItem(-1,TODO(todo)) } else {errorI += 1}
|
||||
|
||||
if errorI == 2 {return errors.New("don't match today and tomorrow")}
|
||||
|
||||
|
||||
if todo.Props["DUE"] != nil {
|
||||
if strings.HasPrefix(todo.Props["DUE"][0].Value, today.Format("20060102")) {
|
||||
m.TodayTab.InsertItem(-1, TODO(todo))
|
||||
//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
|
||||
}
|
||||
|
||||
if errorI == 2 {
|
||||
// return errors.New("don't match today and tomorrow") // debug???
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
183
tui.go
183
tui.go
@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
// "fmt"
|
||||
// "os"
|
||||
"time"
|
||||
// "time"
|
||||
// "io"
|
||||
// "strings"
|
||||
|
||||
@ -20,13 +20,10 @@ import (
|
||||
|
||||
// "errors"
|
||||
)
|
||||
//TODO add new TODos
|
||||
//TODO edit TODOs
|
||||
//TODO add search
|
||||
//TODO add custom filter (search that saves with filter)
|
||||
// TODO add tabs for days/searcf/filter
|
||||
|
||||
|
||||
//TODO NEW FEATURE IMPORTANT - list today/tomorrow - edit TODOs
|
||||
//TODO NEW FEATURE - add search
|
||||
//TODO NEW FEATURE - add custom filter (search that saves with filter)
|
||||
// TODO NEW FEATURE - add tabs for days/searcf/filter?
|
||||
|
||||
|
||||
|
||||
@ -34,8 +31,7 @@ import (
|
||||
type errMsg struct {message string}
|
||||
|
||||
|
||||
//TODO fix multiple errHandlers
|
||||
//TODO rm me
|
||||
//TODO fix multiple errHandlers, rm m.errHandler, rename CLI errHandler (in main.go). rename errHandler_tui to errHandler. Rename ebery call
|
||||
func (m model) errHandler(err error,desc string) (tea.Cmd) {
|
||||
if err != nil {
|
||||
output := desc+": "+err.Error()
|
||||
@ -71,32 +67,39 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch m.ActiveWindow {
|
||||
case "tomorrow": //TODO rm me debug
|
||||
case "a":
|
||||
m.ActiveWindow = "addTODO"
|
||||
return m, nil
|
||||
case "today": //TODO rm me debug
|
||||
switch keypress := msg.String(); keypress {
|
||||
case "q", "ctrl+c":
|
||||
// m.quitting = true
|
||||
return m, tea.Quit
|
||||
|
||||
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)
|
||||
case "a":
|
||||
m.ActiveWindow = "addTODO"
|
||||
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":
|
||||
switch keypress := msg.String(); keypress {
|
||||
@ -124,41 +127,108 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
case "login":
|
||||
switch keypress := msg.String(); keypress {
|
||||
case "ctrl+c", "q":
|
||||
return m, tea.Quit
|
||||
// case "ctrl+c", "q"://are you stupid?
|
||||
// return m, tea.Quit
|
||||
case "enter":
|
||||
if m.focused == len(m.loginInputs)-1 {
|
||||
//TODO check that we have all fields not empty and notificate about it
|
||||
//TODO submit
|
||||
for i := range m.loginInputs {
|
||||
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.Username = m.loginInputs[login].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()
|
||||
if err != nil {return m, m.errHandler(err,"Failed to authenticate")}
|
||||
// try login -> choose calendar -> store -> move to getting stuff
|
||||
return m, nil
|
||||
// return m, tea.Quit
|
||||
}
|
||||
m.nextInput()
|
||||
m.nextLoginInput()
|
||||
case "shift+tab", "up":
|
||||
m.prevInput()
|
||||
// buttonStyle = lipgloss.NewStyle()
|
||||
m.prevLoginInput()
|
||||
case "tab", "down":
|
||||
m.nextInput()
|
||||
// buttonStyle = buttonStyle.Background(lipgloss.Color("#7D56F4"))
|
||||
m.nextLoginInput()
|
||||
}
|
||||
for i := range m.loginInputs {
|
||||
m.loginInputs[i].Blur()
|
||||
}
|
||||
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 {
|
||||
@ -185,17 +255,19 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// TODO add new element
|
||||
// return m, tea.Quit
|
||||
// }
|
||||
case "t":
|
||||
// TODO add new element
|
||||
return m, tea.Quit
|
||||
// case "t":
|
||||
// // TODO add new element
|
||||
// return m, tea.Quit
|
||||
}
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
|
||||
switch m.ActiveWindow {
|
||||
//TODO BUG - addTODO/login - output render breaks a bit if errorHandler appeared on screen
|
||||
// case "login": //TODO
|
||||
// m.TodayTab.SetSize(msg.Width-h, msg.Height-v)
|
||||
// case "calendarChoose": //TODO
|
||||
// case "addTODO": //TODO
|
||||
// m.calendarList.SetWidth(msg.Width)
|
||||
case "today":
|
||||
h, v := docStyle.GetFrameSize()
|
||||
@ -218,6 +290,16 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
for i := range m.loginInputs {
|
||||
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":
|
||||
m.calendarList, cmd = m.calendarList.Update(msg)
|
||||
case "today":
|
||||
@ -228,6 +310,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case "": //exit on key press
|
||||
switch tmp := msg.(type) { //TODO debug and optimize
|
||||
case tea.KeyMsg:
|
||||
// TODO exit on key press
|
||||
_ = tmp
|
||||
if m.LoggedIn {
|
||||
m.ActiveWindow = "today"
|
||||
@ -257,6 +340,18 @@ func (m model) View() string {
|
||||
// MarginRight(width/3)
|
||||
tabOutput = loginStyle.Render(m.RenderLogin())
|
||||
// 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":
|
||||
width, height, _ := term.GetSize(0)
|
||||
width -= 2
|
||||
|
Loading…
Reference in New Issue
Block a user