mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
Create generalized todo module (#982)
* Create generalized todo module Makes existing modules "backends" This gives us a consistent interface * Fix check issues * Fix lint errors * Make changes to stay on wtfutil todoist fork
This commit is contained in:
parent
38e21605a4
commit
3c63eee8d3
@ -59,10 +59,9 @@ import (
|
||||
"github.com/wtfutil/wtf/modules/subreddit"
|
||||
"github.com/wtfutil/wtf/modules/textfile"
|
||||
"github.com/wtfutil/wtf/modules/todo"
|
||||
"github.com/wtfutil/wtf/modules/todoist"
|
||||
"github.com/wtfutil/wtf/modules/todo_plus"
|
||||
"github.com/wtfutil/wtf/modules/transmission"
|
||||
"github.com/wtfutil/wtf/modules/travisci"
|
||||
"github.com/wtfutil/wtf/modules/trello"
|
||||
"github.com/wtfutil/wtf/modules/twitch"
|
||||
"github.com/wtfutil/wtf/modules/twitter"
|
||||
"github.com/wtfutil/wtf/modules/twitterstats"
|
||||
@ -269,9 +268,12 @@ func MakeWidget(
|
||||
case "todo":
|
||||
settings := todo.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = todo.NewWidget(app, pages, settings)
|
||||
case "todo_plus":
|
||||
settings := todo_plus.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = todo_plus.NewWidget(app, pages, settings)
|
||||
case "todoist":
|
||||
settings := todoist.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = todoist.NewWidget(app, pages, settings)
|
||||
settings := todo_plus.FromTodoist(moduleName, moduleConfig, config)
|
||||
widget = todo_plus.NewWidget(app, pages, settings)
|
||||
case "transmission":
|
||||
settings := transmission.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = transmission.NewWidget(app, pages, settings)
|
||||
@ -279,8 +281,8 @@ func MakeWidget(
|
||||
settings := travisci.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = travisci.NewWidget(app, pages, settings)
|
||||
case "trello":
|
||||
settings := trello.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = trello.NewWidget(app, settings)
|
||||
settings := todo_plus.FromTrello(moduleName, moduleConfig, config)
|
||||
widget = todo_plus.NewWidget(app, pages, settings)
|
||||
case "twitch":
|
||||
settings := twitch.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = twitch.NewWidget(app, pages, settings)
|
||||
|
16
modules/todo_plus/backend/backend.go
Normal file
16
modules/todo_plus/backend/backend.go
Normal file
@ -0,0 +1,16 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"github.com/olebedev/config"
|
||||
)
|
||||
|
||||
type Backend interface {
|
||||
Title() string
|
||||
Setup(*config.Config)
|
||||
BuildProjects() []*Project
|
||||
GetProject(string) *Project
|
||||
LoadTasks(string) ([]Task, error)
|
||||
CloseTask(*Task) error
|
||||
DeleteTask(*Task) error
|
||||
Sources() []string
|
||||
}
|
66
modules/todo_plus/backend/project.go
Normal file
66
modules/todo_plus/backend/project.go
Normal file
@ -0,0 +1,66 @@
|
||||
package backend
|
||||
|
||||
type Task struct {
|
||||
ID string
|
||||
Completed bool
|
||||
Name string
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
ID string
|
||||
Name string
|
||||
|
||||
Index int
|
||||
Tasks []Task
|
||||
Err error
|
||||
backend Backend
|
||||
}
|
||||
|
||||
func (proj *Project) IsLast() bool {
|
||||
return proj.Index >= len(proj.Tasks)-1
|
||||
}
|
||||
|
||||
func (proj *Project) loadTasks() {
|
||||
Tasks, err := proj.backend.LoadTasks(proj.ID)
|
||||
proj.Err = err
|
||||
proj.Tasks = Tasks
|
||||
}
|
||||
|
||||
func (proj *Project) LongestLine() int {
|
||||
maxLen := 0
|
||||
|
||||
for _, task := range proj.Tasks {
|
||||
if len(task.Name) > maxLen {
|
||||
maxLen = len(task.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return maxLen
|
||||
}
|
||||
|
||||
func (proj *Project) currentTask() *Task {
|
||||
if proj.Index < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &proj.Tasks[proj.Index]
|
||||
}
|
||||
|
||||
func (proj *Project) CloseSelectedTask() {
|
||||
currTask := proj.currentTask()
|
||||
|
||||
if currTask != nil {
|
||||
_ = proj.backend.CloseTask(currTask)
|
||||
proj.loadTasks()
|
||||
}
|
||||
}
|
||||
|
||||
func (proj *Project) DeleteSelectedTask() {
|
||||
currTask := proj.currentTask()
|
||||
|
||||
if currTask != nil {
|
||||
_ = proj.backend.DeleteTask(currTask)
|
||||
|
||||
proj.loadTasks()
|
||||
}
|
||||
}
|
108
modules/todo_plus/backend/todoist.go
Normal file
108
modules/todo_plus/backend/todoist.go
Normal file
@ -0,0 +1,108 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/olebedev/config"
|
||||
"github.com/wtfutil/todoist"
|
||||
)
|
||||
|
||||
type Todoist struct {
|
||||
projects []interface{}
|
||||
}
|
||||
|
||||
func (todo *Todoist) Title() string {
|
||||
return "Todoist"
|
||||
}
|
||||
|
||||
func (todo *Todoist) Setup(config *config.Config) {
|
||||
todoist.Token = config.UString("apiKey")
|
||||
todo.projects = config.UList("projects")
|
||||
}
|
||||
|
||||
func (todo *Todoist) BuildProjects() []*Project {
|
||||
projects := []*Project{}
|
||||
|
||||
for _, id := range todo.projects {
|
||||
i := strconv.Itoa(id.(int))
|
||||
proj := todo.GetProject(i)
|
||||
projects = append(projects, proj)
|
||||
}
|
||||
return projects
|
||||
}
|
||||
|
||||
func (todo *Todoist) GetProject(id string) *Project {
|
||||
// Todoist seems to experience a lot of network issues on their side
|
||||
// If we can't connect, handle it with an empty project until we can
|
||||
proj := &Project{
|
||||
Index: -1,
|
||||
backend: todo,
|
||||
}
|
||||
i64, _ := strconv.ParseUint(id, 10, 32)
|
||||
i := uint(i64)
|
||||
project, err := todoist.GetProject(i)
|
||||
if err != nil {
|
||||
proj.Err = err
|
||||
return proj
|
||||
}
|
||||
|
||||
proj.ID = strconv.FormatUint(uint64(project.ID), 10)
|
||||
proj.Name = project.Name
|
||||
|
||||
tasks, err := todo.LoadTasks(proj.ID)
|
||||
proj.Err = err
|
||||
proj.Tasks = tasks
|
||||
|
||||
return proj
|
||||
}
|
||||
|
||||
func toTask(task todoist.Task) Task {
|
||||
id := strconv.FormatUint(uint64(task.ID), 10)
|
||||
return Task{
|
||||
ID: id,
|
||||
Completed: task.Completed,
|
||||
Name: task.Content,
|
||||
}
|
||||
}
|
||||
|
||||
func (todo *Todoist) LoadTasks(id string) ([]Task, error) {
|
||||
tasks, err := todoist.ListTask(todoist.QueryParam{"project_id": id})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var finalTasks []Task
|
||||
for _, item := range tasks {
|
||||
finalTasks = append(finalTasks, toTask(item))
|
||||
}
|
||||
return finalTasks, nil
|
||||
}
|
||||
|
||||
func (todo *Todoist) CloseTask(task *Task) error {
|
||||
if task != nil {
|
||||
i64, _ := strconv.ParseUint(task.ID, 10, 32)
|
||||
i := uint(i64)
|
||||
internal := todoist.Task{ID: i}
|
||||
return internal.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (todo *Todoist) DeleteTask(task *Task) error {
|
||||
if task != nil {
|
||||
i64, _ := strconv.ParseUint(task.ID, 10, 32)
|
||||
i := uint(i64)
|
||||
internal := todoist.Task{ID: i}
|
||||
return internal.Delete()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (todo *Todoist) Sources() []string {
|
||||
var result []string
|
||||
for _, id := range todo.projects {
|
||||
i := strconv.Itoa(id.(int))
|
||||
result = append(result, i)
|
||||
}
|
||||
return result
|
||||
}
|
170
modules/todo_plus/backend/trello.go
Normal file
170
modules/todo_plus/backend/trello.go
Normal file
@ -0,0 +1,170 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/adlio/trello"
|
||||
"github.com/olebedev/config"
|
||||
)
|
||||
|
||||
type Trello struct {
|
||||
username string
|
||||
boardName string
|
||||
client *trello.Client
|
||||
board string
|
||||
projects []interface{}
|
||||
}
|
||||
|
||||
func (todo *Trello) Title() string {
|
||||
return "Trello"
|
||||
}
|
||||
|
||||
func (todo *Trello) Setup(config *config.Config) {
|
||||
todo.username = config.UString("username")
|
||||
todo.boardName = config.UString("board")
|
||||
todo.client = trello.NewClient(
|
||||
config.UString("apiKey"),
|
||||
config.UString("accessToken"),
|
||||
)
|
||||
board, err := getBoardID(todo.client, todo.username, todo.boardName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
todo.board = board
|
||||
todo.projects = config.UList("lists")
|
||||
}
|
||||
|
||||
func getBoardID(client *trello.Client, username, boardName string) (string, error) {
|
||||
member, err := client.GetMember(username, trello.Defaults())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
boards, err := member.GetBoards(trello.Defaults())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, board := range boards {
|
||||
if board.Name == boardName {
|
||||
return board.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not find board with name %s", boardName)
|
||||
}
|
||||
|
||||
func getListId(client *trello.Client, boardID string, listName string) (string, error) {
|
||||
board, err := client.GetBoard(boardID, trello.Defaults())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
boardLists, err := board.GetLists(trello.Defaults())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, list := range boardLists {
|
||||
if list.Name == listName {
|
||||
return list.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func getCardsOnList(client *trello.Client, listID string) ([]*trello.Card, error) {
|
||||
list, err := client.GetList(listID, trello.Defaults())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cards, err := list.GetCards(trello.Defaults())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cards, nil
|
||||
}
|
||||
|
||||
func (todo *Trello) BuildProjects() []*Project {
|
||||
projects := []*Project{}
|
||||
|
||||
for _, id := range todo.projects {
|
||||
proj := todo.GetProject(id.(string))
|
||||
projects = append(projects, proj)
|
||||
}
|
||||
return projects
|
||||
}
|
||||
|
||||
func (todo *Trello) GetProject(id string) *Project {
|
||||
proj := &Project{
|
||||
Index: -1,
|
||||
backend: todo,
|
||||
}
|
||||
|
||||
listId, err := getListId(todo.client, todo.board, id)
|
||||
if err != nil {
|
||||
proj.Err = err
|
||||
return proj
|
||||
}
|
||||
proj.ID = listId
|
||||
proj.Name = id
|
||||
|
||||
tasks, err := todo.LoadTasks(listId)
|
||||
proj.Err = err
|
||||
proj.Tasks = tasks
|
||||
|
||||
return proj
|
||||
}
|
||||
|
||||
func fromTrello(task *trello.Card) Task {
|
||||
return Task{
|
||||
ID: task.ID,
|
||||
Completed: task.Closed,
|
||||
Name: task.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func (todo *Trello) LoadTasks(id string) ([]Task, error) {
|
||||
tasks, err := getCardsOnList(todo.client, id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var finalTasks []Task
|
||||
for _, item := range tasks {
|
||||
finalTasks = append(finalTasks, fromTrello(item))
|
||||
}
|
||||
return finalTasks, nil
|
||||
}
|
||||
|
||||
func (todo *Trello) CloseTask(task *Task) error {
|
||||
args := trello.Arguments{
|
||||
"closed": "true",
|
||||
}
|
||||
if task != nil {
|
||||
// Card has an internal client rep which we can't access
|
||||
// Just force a lookup
|
||||
internal, err := todo.client.GetCard(task.ID, trello.Arguments{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return internal.Update(args)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (todo *Trello) DeleteTask(task *Task) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (todo *Trello) Sources() []string {
|
||||
var result []string
|
||||
for _, id := range todo.projects {
|
||||
result = append(result, id.(string))
|
||||
}
|
||||
return result
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package todoist
|
||||
package todo_plus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -14,27 +14,26 @@ func (widget *Widget) content() (string, string, bool) {
|
||||
return widget.CommonSettings().Title, "", false
|
||||
}
|
||||
|
||||
if proj.err != nil {
|
||||
return widget.CommonSettings().Title, proj.err.Error(), true
|
||||
if proj.Err != nil {
|
||||
return widget.CommonSettings().Title, proj.Err.Error(), true
|
||||
}
|
||||
|
||||
title := fmt.Sprintf(
|
||||
"[%s]%s[white]",
|
||||
widget.settings.common.Colors.TextTheme.Title,
|
||||
proj.Project.Name,
|
||||
)
|
||||
proj.Name)
|
||||
|
||||
str := ""
|
||||
|
||||
for idx, item := range proj.tasks {
|
||||
for idx, item := range proj.Tasks {
|
||||
row := fmt.Sprintf(
|
||||
`[%s]| | %s[%s]`,
|
||||
widget.RowColor(idx),
|
||||
tview.Escape(item.Content),
|
||||
tview.Escape(item.Name),
|
||||
widget.RowColor(idx),
|
||||
)
|
||||
|
||||
str += utils.HighlightableHelper(widget.View, row, idx, len(item.Content))
|
||||
str += utils.HighlightableHelper(widget.View, row, idx, len(item.Name))
|
||||
}
|
||||
return title, str, false
|
||||
}
|
@ -1,21 +1,21 @@
|
||||
package todoist
|
||||
package todo_plus
|
||||
|
||||
import "github.com/gdamore/tcell"
|
||||
|
||||
func (widget *Widget) initializeKeyboardControls() {
|
||||
widget.InitializeCommonControls(widget.Refresh)
|
||||
|
||||
widget.SetKeyboardChar("c", widget.Close, "Close item")
|
||||
widget.SetKeyboardChar("d", widget.Delete, "Delete item")
|
||||
widget.SetKeyboardChar("h", widget.PrevSource, "Select previous project")
|
||||
widget.SetKeyboardChar("j", widget.Prev, "Select previous item")
|
||||
widget.SetKeyboardChar("k", widget.Next, "Select next item")
|
||||
widget.SetKeyboardChar("h", widget.PrevSource, "Select previous project")
|
||||
widget.SetKeyboardChar("c", widget.Close, "Close item")
|
||||
widget.SetKeyboardChar("l", widget.NextSource, "Select next project")
|
||||
widget.SetKeyboardChar("u", widget.Unselect, "Clear selection")
|
||||
|
||||
widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item")
|
||||
widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item")
|
||||
widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection")
|
||||
widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous project")
|
||||
widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next project")
|
||||
widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item")
|
||||
}
|
81
modules/todo_plus/settings.go
Normal file
81
modules/todo_plus/settings.go
Normal file
@ -0,0 +1,81 @@
|
||||
package todo_plus
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/olebedev/config"
|
||||
"github.com/wtfutil/wtf/cfg"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTitle = "Todo"
|
||||
defaultFocusable = true
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
common *cfg.Common
|
||||
|
||||
backendType string
|
||||
backendSettings *config.Config
|
||||
}
|
||||
|
||||
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
|
||||
|
||||
backend, _ := ymlConfig.Get("backendSettings")
|
||||
|
||||
settings := Settings{
|
||||
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
|
||||
|
||||
backendType: ymlConfig.UString("backendType"),
|
||||
backendSettings: backend,
|
||||
}
|
||||
|
||||
return &settings
|
||||
}
|
||||
|
||||
func FromTodoist(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
|
||||
apiKey := ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_TODOIST_TOKEN")))
|
||||
cfg.ModuleSecret(name, globalConfig, &apiKey).Load()
|
||||
projects := ymlConfig.UList("projects")
|
||||
backend, _ := config.ParseYaml("apiKey: " + apiKey)
|
||||
_ = backend.Set(".projects", projects)
|
||||
|
||||
settings := Settings{
|
||||
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
|
||||
|
||||
backendType: "todoist",
|
||||
backendSettings: backend,
|
||||
}
|
||||
|
||||
return &settings
|
||||
}
|
||||
|
||||
func FromTrello(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
|
||||
|
||||
accessToken := ymlConfig.UString("accessToken", ymlConfig.UString("apikey", os.Getenv("WTF_TRELLO_ACCESS_TOKEN")))
|
||||
apiKey := ymlConfig.UString("apiKey", os.Getenv("WTF_TRELLO_API_KEY"))
|
||||
cfg.ModuleSecret(name, globalConfig, &apiKey).Load()
|
||||
board := ymlConfig.UString("board")
|
||||
username := ymlConfig.UString("username")
|
||||
var lists []interface{}
|
||||
list, err := ymlConfig.String("list")
|
||||
if err == nil {
|
||||
lists = append(lists, list)
|
||||
} else {
|
||||
lists = ymlConfig.UList("list")
|
||||
}
|
||||
backend, _ := config.ParseYaml("apiKey: " + apiKey)
|
||||
_ = backend.Set(".accessToken", accessToken)
|
||||
_ = backend.Set(".board", board)
|
||||
_ = backend.Set(".username", username)
|
||||
_ = backend.Set(".lists", lists)
|
||||
|
||||
settings := Settings{
|
||||
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
|
||||
|
||||
backendType: "trello",
|
||||
backendSettings: backend,
|
||||
}
|
||||
|
||||
return &settings
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package todoist
|
||||
package todo_plus
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
"github.com/wtfutil/todoist"
|
||||
"github.com/wtfutil/wtf/modules/todo_plus/backend"
|
||||
"github.com/wtfutil/wtf/view"
|
||||
)
|
||||
|
||||
@ -12,8 +14,9 @@ type Widget struct {
|
||||
view.MultiSourceWidget
|
||||
view.ScrollableWidget
|
||||
|
||||
projects []*Project
|
||||
projects []*backend.Project
|
||||
settings *Settings
|
||||
backend backend.Backend
|
||||
}
|
||||
|
||||
// NewWidget creates a new instance of a widget
|
||||
@ -26,8 +29,9 @@ func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *
|
||||
settings: settings,
|
||||
}
|
||||
|
||||
widget.loadAPICredentials()
|
||||
widget.loadProjects()
|
||||
widget.backend = getBackend(settings.backendType)
|
||||
widget.backend.Setup(settings.backendSettings)
|
||||
widget.CommonSettings().Title = widget.backend.Title()
|
||||
|
||||
widget.SetRenderFunction(widget.display)
|
||||
widget.initializeKeyboardControls()
|
||||
@ -39,13 +43,28 @@ func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *
|
||||
return &widget
|
||||
}
|
||||
|
||||
func getBackend(backendType string) backend.Backend {
|
||||
switch backendType {
|
||||
case "trello":
|
||||
backend := &backend.Trello{}
|
||||
return backend
|
||||
case "todoist":
|
||||
backend := &backend.Todoist{}
|
||||
return backend
|
||||
default:
|
||||
log.Fatal(backendType + " is not a supported backend")
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* -------------------- Exported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) CurrentProject() *Project {
|
||||
func (widget *Widget) CurrentProject() *backend.Project {
|
||||
return widget.ProjectAt(widget.Idx)
|
||||
}
|
||||
|
||||
func (widget *Widget) ProjectAt(idx int) *Project {
|
||||
func (widget *Widget) ProjectAt(idx int) *backend.Project {
|
||||
if len(widget.projects) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -54,14 +73,13 @@ func (widget *Widget) ProjectAt(idx int) *Project {
|
||||
}
|
||||
|
||||
func (widget *Widget) Refresh() {
|
||||
if widget.Disabled() || widget.CurrentProject() == nil {
|
||||
widget.SetItemCount(0)
|
||||
if widget.Disabled() {
|
||||
return
|
||||
}
|
||||
|
||||
widget.loadProjects()
|
||||
|
||||
widget.SetItemCount(len(widget.CurrentProject().tasks))
|
||||
widget.projects = widget.backend.BuildProjects()
|
||||
widget.Sources = widget.backend.Sources()
|
||||
widget.SetItemCount(len(widget.CurrentProject().Tasks))
|
||||
widget.display()
|
||||
}
|
||||
|
||||
@ -71,74 +89,57 @@ func (widget *Widget) HelpText() string {
|
||||
|
||||
func (widget *Widget) NextSource() {
|
||||
widget.MultiSourceWidget.NextSource()
|
||||
widget.Selected = widget.CurrentProject().index
|
||||
widget.SetItemCount(len(widget.CurrentProject().tasks))
|
||||
widget.Selected = widget.CurrentProject().Index
|
||||
widget.SetItemCount(len(widget.CurrentProject().Tasks))
|
||||
widget.RenderFunction()
|
||||
}
|
||||
|
||||
func (widget *Widget) PrevSource() {
|
||||
widget.MultiSourceWidget.PrevSource()
|
||||
widget.Selected = widget.CurrentProject().index
|
||||
widget.SetItemCount(len(widget.CurrentProject().tasks))
|
||||
widget.Selected = widget.CurrentProject().Index
|
||||
widget.SetItemCount(len(widget.CurrentProject().Tasks))
|
||||
widget.RenderFunction()
|
||||
}
|
||||
|
||||
func (widget *Widget) Prev() {
|
||||
widget.ScrollableWidget.Prev()
|
||||
widget.CurrentProject().index = widget.Selected
|
||||
widget.CurrentProject().Index = widget.Selected
|
||||
}
|
||||
|
||||
func (widget *Widget) Next() {
|
||||
widget.ScrollableWidget.Next()
|
||||
widget.CurrentProject().index = widget.Selected
|
||||
widget.CurrentProject().Index = widget.Selected
|
||||
}
|
||||
|
||||
func (widget *Widget) Unselect() {
|
||||
widget.ScrollableWidget.Unselect()
|
||||
widget.CurrentProject().index = -1
|
||||
widget.CurrentProject().Index = -1
|
||||
widget.RenderFunction()
|
||||
}
|
||||
|
||||
/* -------------------- Keyboard Movement -------------------- */
|
||||
|
||||
// Close closes the currently-selected task in the currently-selected project
|
||||
func (widget *Widget) Close() {
|
||||
widget.CurrentProject().closeSelectedTask()
|
||||
widget.SetItemCount(len(widget.CurrentProject().tasks))
|
||||
func (w *Widget) Close() {
|
||||
w.CurrentProject().CloseSelectedTask()
|
||||
w.SetItemCount(len(w.CurrentProject().Tasks))
|
||||
|
||||
if widget.CurrentProject().isLast() {
|
||||
widget.Prev()
|
||||
if w.CurrentProject().IsLast() {
|
||||
w.Prev()
|
||||
return
|
||||
}
|
||||
widget.CurrentProject().index = widget.Selected
|
||||
widget.RenderFunction()
|
||||
w.CurrentProject().Index = w.Selected
|
||||
w.RenderFunction()
|
||||
}
|
||||
|
||||
// Delete deletes the currently-selected task in the currently-selected project
|
||||
func (widget *Widget) Delete() {
|
||||
widget.CurrentProject().deleteSelectedTask()
|
||||
widget.SetItemCount(len(widget.CurrentProject().tasks))
|
||||
func (w *Widget) Delete() {
|
||||
w.CurrentProject().DeleteSelectedTask()
|
||||
w.SetItemCount(len(w.CurrentProject().Tasks))
|
||||
|
||||
if widget.CurrentProject().isLast() {
|
||||
widget.Prev()
|
||||
if w.CurrentProject().IsLast() {
|
||||
w.Prev()
|
||||
}
|
||||
widget.CurrentProject().index = widget.Selected
|
||||
widget.RenderFunction()
|
||||
}
|
||||
|
||||
/* -------------------- Unexported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) loadAPICredentials() {
|
||||
todoist.Token = widget.settings.apiKey
|
||||
}
|
||||
|
||||
func (widget *Widget) loadProjects() {
|
||||
projects := []*Project{}
|
||||
|
||||
for _, id := range widget.settings.projects {
|
||||
proj := NewProject(id)
|
||||
projects = append(projects, proj)
|
||||
}
|
||||
|
||||
widget.projects = projects
|
||||
w.CurrentProject().Index = w.Selected
|
||||
w.RenderFunction()
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
package todoist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wtfutil/todoist"
|
||||
)
|
||||
|
||||
type Project struct {
|
||||
todoist.Project
|
||||
|
||||
index int
|
||||
tasks []todoist.Task
|
||||
err error
|
||||
}
|
||||
|
||||
func NewProject(id uint) *Project {
|
||||
// Todoist seems to experience a lot of network issues on their side
|
||||
// If we can't connect, handle it with an empty project until we can
|
||||
project, err := todoist.GetProject(id)
|
||||
proj := &Project{
|
||||
index: -1,
|
||||
}
|
||||
if err != nil {
|
||||
proj.err = err
|
||||
return proj
|
||||
}
|
||||
|
||||
proj.Project = project
|
||||
|
||||
proj.loadTasks()
|
||||
|
||||
return proj
|
||||
}
|
||||
|
||||
func (proj *Project) isLast() bool {
|
||||
return proj.index >= len(proj.tasks)-1
|
||||
}
|
||||
|
||||
func (proj *Project) loadTasks() {
|
||||
tasks, err := todoist.ListTask(todoist.QueryParam{"project_id": fmt.Sprintf("%d", proj.ID)})
|
||||
if err != nil {
|
||||
proj.err = err
|
||||
proj.tasks = nil
|
||||
} else {
|
||||
proj.err = nil
|
||||
proj.tasks = tasks
|
||||
}
|
||||
}
|
||||
|
||||
func (proj *Project) LongestLine() int {
|
||||
maxLen := 0
|
||||
|
||||
for _, task := range proj.tasks {
|
||||
if len(task.Content) > maxLen {
|
||||
maxLen = len(task.Content)
|
||||
}
|
||||
}
|
||||
|
||||
return maxLen
|
||||
}
|
||||
|
||||
func (proj *Project) currentTask() *todoist.Task {
|
||||
if proj.index < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &proj.tasks[proj.index]
|
||||
}
|
||||
|
||||
func (proj *Project) closeSelectedTask() {
|
||||
currTask := proj.currentTask()
|
||||
|
||||
if currTask != nil {
|
||||
if err := currTask.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
proj.loadTasks()
|
||||
}
|
||||
}
|
||||
|
||||
func (proj *Project) deleteSelectedTask() {
|
||||
currTask := proj.currentTask()
|
||||
|
||||
if currTask != nil {
|
||||
if err := currTask.Delete(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
proj.loadTasks()
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package todoist
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/olebedev/config"
|
||||
"github.com/wtfutil/wtf/cfg"
|
||||
"github.com/wtfutil/wtf/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFocusable = true
|
||||
defaultTitle = "Todoist"
|
||||
)
|
||||
|
||||
// Settings defines the configuration properties for this module
|
||||
type Settings struct {
|
||||
common *cfg.Common
|
||||
|
||||
apiKey string `help:"Your Todoist API key"`
|
||||
projects []uint
|
||||
}
|
||||
|
||||
// NewSettingsFromYAML creates a new settings instance from a YAML config block
|
||||
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
|
||||
settings := Settings{
|
||||
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
|
||||
|
||||
apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_TODOIST_TOKEN"))),
|
||||
projects: utils.IntsToUints(utils.ToInts(ymlConfig.UList("projects"))),
|
||||
}
|
||||
|
||||
cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load()
|
||||
|
||||
return &settings
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package trello
|
||||
|
||||
type TrelloCard struct {
|
||||
ID string
|
||||
Name string
|
||||
List string
|
||||
Description string
|
||||
}
|
||||
|
||||
type TrelloList struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
package trello
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/adlio/trello"
|
||||
)
|
||||
|
||||
func GetCards(client *trello.Client, username string, boardName string, listNames []string) (*SearchResult, error) {
|
||||
boardID, err := getBoardID(client, username, boardName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lists, err := getLists(client, boardID, listNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
searchResult := &SearchResult{Total: 0}
|
||||
searchResult.TrelloCards = make(map[string][]TrelloCard)
|
||||
|
||||
for _, list := range lists {
|
||||
cards, err := getCardsOnList(client, list.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
searchResult.Total += len(cards)
|
||||
cardArray := make([]TrelloCard, 0)
|
||||
|
||||
for _, card := range cards {
|
||||
trelloCard := TrelloCard{
|
||||
ID: card.ID,
|
||||
List: list.Name,
|
||||
Name: card.Name,
|
||||
Description: card.Desc,
|
||||
}
|
||||
cardArray = append(cardArray, trelloCard)
|
||||
}
|
||||
|
||||
searchResult.TrelloCards[list.Name] = cardArray
|
||||
}
|
||||
|
||||
return searchResult, nil
|
||||
}
|
||||
|
||||
func getBoardID(client *trello.Client, username, boardName string) (string, error) {
|
||||
member, err := client.GetMember(username, trello.Defaults())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
boards, err := member.GetBoards(trello.Defaults())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, board := range boards {
|
||||
if board.Name == boardName {
|
||||
return board.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not find board with name %s", boardName)
|
||||
}
|
||||
|
||||
func getLists(client *trello.Client, boardID string, listNames []string) ([]TrelloList, error) {
|
||||
comparison := make(map[string]string, len(listNames))
|
||||
results := []TrelloList{}
|
||||
//convert to a map for comparison
|
||||
for _, item := range listNames {
|
||||
comparison[item] = ""
|
||||
}
|
||||
board, err := client.GetBoard(boardID, trello.Defaults())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
boardLists, err := board.GetLists(trello.Defaults())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, list := range boardLists {
|
||||
if _, ok := comparison[list.Name]; ok {
|
||||
results = append(results, TrelloList{ID: list.ID, Name: list.Name})
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func getCardsOnList(client *trello.Client, listID string) ([]*trello.Card, error) {
|
||||
list, err := client.GetList(listID, trello.Defaults())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cards, err := list.GetCards(trello.Defaults())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cards, nil
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package trello
|
||||
|
||||
type SearchResult struct {
|
||||
Total int
|
||||
TrelloCards map[string][]TrelloCard
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package trello
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/olebedev/config"
|
||||
"github.com/wtfutil/wtf/cfg"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFocusable = false
|
||||
defaultTitle = "Trello"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
common *cfg.Common
|
||||
|
||||
accessToken string
|
||||
apiKey string
|
||||
board string
|
||||
list []string
|
||||
username string
|
||||
}
|
||||
|
||||
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
|
||||
settings := Settings{
|
||||
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
|
||||
|
||||
accessToken: ymlConfig.UString("accessToken", ymlConfig.UString("apikey", os.Getenv("WTF_TRELLO_ACCESS_TOKEN"))),
|
||||
apiKey: ymlConfig.UString("apiKey", os.Getenv("WTF_TRELLO_API_KEY")),
|
||||
board: ymlConfig.UString("board"),
|
||||
username: ymlConfig.UString("username"),
|
||||
}
|
||||
|
||||
cfg.ModuleSecret(name+"-api", globalConfig, &settings.apiKey).Load()
|
||||
cfg.ModuleSecret(name+"-access", globalConfig, &settings.accessToken).Load()
|
||||
|
||||
settings.list = buildLists(ymlConfig, globalConfig)
|
||||
|
||||
return &settings
|
||||
}
|
||||
|
||||
func buildLists(ymlConfig *config.Config, globalConfig *config.Config) []string {
|
||||
lists := []string{}
|
||||
|
||||
// Single list
|
||||
list, err := ymlConfig.String("list")
|
||||
if err == nil {
|
||||
lists = append(lists, list)
|
||||
return lists
|
||||
}
|
||||
|
||||
// Array of lists
|
||||
listList := ymlConfig.UList("list")
|
||||
for _, listName := range listList {
|
||||
if list, ok := listName.(string); ok {
|
||||
lists = append(lists, list)
|
||||
}
|
||||
}
|
||||
|
||||
return lists
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
package trello
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/adlio/trello"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/wtfutil/wtf/view"
|
||||
)
|
||||
|
||||
type Widget struct {
|
||||
view.TextWidget
|
||||
|
||||
settings *Settings
|
||||
}
|
||||
|
||||
func NewWidget(app *tview.Application, settings *Settings) *Widget {
|
||||
widget := Widget{
|
||||
TextWidget: view.NewTextWidget(app, settings.common),
|
||||
|
||||
settings: settings,
|
||||
}
|
||||
|
||||
return &widget
|
||||
}
|
||||
|
||||
/* -------------------- Exported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) Refresh() {
|
||||
widget.Redraw(widget.content)
|
||||
}
|
||||
|
||||
/* -------------------- Unexported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) content() (string, string, bool) {
|
||||
|
||||
client := trello.NewClient(
|
||||
widget.settings.apiKey,
|
||||
widget.settings.accessToken,
|
||||
)
|
||||
|
||||
// Get the cards
|
||||
searchResult, err := GetCards(
|
||||
client,
|
||||
widget.settings.username,
|
||||
widget.settings.board,
|
||||
widget.settings.list,
|
||||
)
|
||||
|
||||
var title string
|
||||
content := ""
|
||||
|
||||
wrap := false
|
||||
if err != nil {
|
||||
wrap = true
|
||||
title = widget.CommonSettings().Title
|
||||
content = err.Error()
|
||||
} else {
|
||||
title = fmt.Sprintf(
|
||||
"[white]%s: [green]%s ",
|
||||
widget.CommonSettings().Title,
|
||||
widget.settings.board,
|
||||
)
|
||||
for list, cardArray := range searchResult.TrelloCards {
|
||||
content += fmt.Sprintf(" [%s]%s[white]\n", widget.settings.common.Colors.Subheading, list)
|
||||
|
||||
for _, card := range cardArray {
|
||||
content += fmt.Sprintf(" %s[white]\n", card.Name)
|
||||
}
|
||||
content = fmt.Sprintf("%s\n", content)
|
||||
}
|
||||
}
|
||||
|
||||
return title, content, wrap
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user