diff --git a/Gopkg.lock b/Gopkg.lock
index 52307702..bfa5b6c7 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -25,6 +25,12 @@
packages = ["."]
revision = "6a9abf92e34f4de62ac671caee3143f10b98892d"
+[[projects]]
+ branch = "master"
+ name = "github.com/darkSasori/todoist"
+ packages = ["."]
+ revision = "ec6b38b374ab9c60cc9716d2083ae66eb9383d03"
+
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
@@ -201,6 +207,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "04892edb6b5f0be61b391ccead307ed15899532db05a17b9e28c00ee32a34861"
+ inputs-digest = "9eaa70ed639c832e3cde26a4270f4c7b9124960952aa76506f702c2c296d5019"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index f36a5e92..7cd20a5e 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -85,6 +85,10 @@
name = "github.com/adlio/trello"
branch = "master"
+[[constraint]]
+ branch = "master"
+ name = "github.com/darkSasori/todoist"
+
[prune]
go-tests = true
unused-packages = true
diff --git a/_site/content/posts/modules/todoist.md b/_site/content/posts/modules/todoist.md
new file mode 100644
index 00000000..bb3beb11
--- /dev/null
+++ b/_site/content/posts/modules/todoist.md
@@ -0,0 +1,88 @@
+---
+title: "Todoist"
+date: 2018-07-05T22:55:55-03:00
+draft: false
+---
+
+Displays all itens on specified project.
+
+
+
+## Source Code
+
+```bash
+wtf/todoist/
+```
+
+## Required ENV Variables
+
+Key: `WTF_TODOIST_TOKEN`
+Value: Your Todoist API Token.
+
+_You can get your API Token at: todoist.com/prefs/integrations._
+
+## Keyboard Commands
+
+Key: `h`
+Action: Show the previous project.
+
+Key: `←`
+Action: Show the previous project.
+
+Key: `l`
+Action: Show the next project.
+
+Key: `→`
+Action: Show the next project.
+
+Key: `j`
+Action: Select the next item in the list.
+
+Key: `↓`
+Action: Select the next item in the list.
+
+Key: `k`
+Action: Select the previous item in the list.
+
+Key: `↑`
+Action: Select the previous item in the list.
+
+Key: `c`
+Action: Close current item.
+
+Key: `d`
+Action: Delete current item.
+
+Key: `r`
+Action: Reload all projects.
+
+## Configuration
+
+```yaml
+todoist:
+ projects:
+ - project_id
+ enabled: true
+ position:
+ height: 1
+ left: 2
+ top: 0
+ width: 1
+ refreshInterval: 3600
+```
+
+### Attributes
+
+`enabled`
+Determines whether or not this module is executed and if its data displayed onscreen.
+Values: `true`, `false`.
+
+`projects`
+The todoist projects to fetch items from.
+
+`refreshInterval`
+How often, in seconds, this module will update its data.
+Values: A positive integer, `0..n`.
+
+`position`
+Where in the grid this module's widget will be displayed.
diff --git a/_site/static/imgs/modules/todoist.png b/_site/static/imgs/modules/todoist.png
new file mode 100644
index 00000000..086d0ec8
Binary files /dev/null and b/_site/static/imgs/modules/todoist.png differ
diff --git a/_site/themes/hyde-hyde/layouts/partials/sidebar.html b/_site/themes/hyde-hyde/layouts/partials/sidebar.html
index 3bb31b75..2da8d975 100644
--- a/_site/themes/hyde-hyde/layouts/partials/sidebar.html
+++ b/_site/themes/hyde-hyde/layouts/partials/sidebar.html
@@ -47,6 +47,7 @@
+
diff --git a/todoist/display.go b/todoist/display.go
new file mode 100644
index 00000000..8cbfa858
--- /dev/null
+++ b/todoist/display.go
@@ -0,0 +1,64 @@
+package todoist
+
+import (
+ "fmt"
+
+ "github.com/gdamore/tcell"
+ "github.com/rivo/tview"
+ "github.com/senorprogrammer/wtf/wtf"
+)
+
+func (w *Widget) display() {
+ if len(w.list) == 0 {
+ return
+ }
+ list := w.list[w.idx]
+
+ w.View.SetTitle(fmt.Sprintf("%s- [green]%s[white] ", w.Name, list.Project.Name))
+ str := wtf.SigilStr(len(w.list), w.idx, w.View) + "\n"
+
+ for index, item := range list.items {
+ if index == list.index {
+ str = str + fmt.Sprintf("[%s]", wtf.Config.UString("wtf.colors.border.focused", "grey"))
+ }
+ str = str + fmt.Sprintf("| | %s[white]\n", tview.Escape(item.Content))
+ }
+
+ w.View.Clear()
+ w.View.SetText(str)
+}
+
+func (w *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey {
+ if len(w.list) == 0 {
+ return event
+ }
+
+ switch string(event.Rune()) {
+ case "r":
+ w.Refresh()
+ return nil
+ case "d":
+ w.Delete()
+ return nil
+ case "c":
+ w.Close()
+ return nil
+ }
+
+ switch fromVim(event) {
+ case tcell.KeyLeft:
+ w.Prev()
+ return nil
+ case tcell.KeyRight:
+ w.Next()
+ return nil
+ case tcell.KeyUp:
+ w.UP()
+ return nil
+ case tcell.KeyDown:
+ w.Down()
+ return nil
+ }
+
+ return event
+}
diff --git a/todoist/list.go b/todoist/list.go
new file mode 100644
index 00000000..81b9d55e
--- /dev/null
+++ b/todoist/list.go
@@ -0,0 +1,79 @@
+package todoist
+
+import (
+ "fmt"
+
+ "github.com/darkSasori/todoist"
+)
+
+type List struct {
+ todoist.Project
+ items []todoist.Task
+ index int
+}
+
+func NewList(id int) *List {
+ project, err := todoist.GetProject(id)
+ if err != nil {
+ panic(err)
+ }
+
+ list := &List{
+ Project: project,
+ index: -1,
+ }
+ list.loadItems()
+ return list
+}
+
+func (l List) isFirst() bool {
+ return l.index == 0
+}
+
+func (l List) isLast() bool {
+ return l.index >= len(l.items)-1
+}
+
+func (l *List) up() {
+ l.index = l.index - 1
+ if l.index < 0 {
+ l.index = len(l.items) - 1
+ }
+}
+
+func (l *List) down() {
+ if l.index == -1 {
+ l.index = 0
+ return
+ }
+
+ l.index = l.index + 1
+ if l.index >= len(l.items) {
+ l.index = 0
+ }
+}
+
+func (l *List) loadItems() {
+ tasks, err := todoist.ListTask(todoist.QueryParam{"project_id": fmt.Sprintf("%d", l.ID)})
+ if err != nil {
+ panic(err)
+ }
+
+ l.items = tasks
+}
+
+func (l *List) close() {
+ if err := l.items[l.index].Close(); err != nil {
+ panic(err)
+ }
+
+ l.loadItems()
+}
+
+func (l *List) delete() {
+ if err := l.items[l.index].Delete(); err != nil {
+ panic(err)
+ }
+
+ l.loadItems()
+}
diff --git a/todoist/widget.go b/todoist/widget.go
new file mode 100644
index 00000000..e40089fa
--- /dev/null
+++ b/todoist/widget.go
@@ -0,0 +1,113 @@
+package todoist
+
+import (
+ "os"
+
+ "github.com/darkSasori/todoist"
+ "github.com/gdamore/tcell"
+ "github.com/rivo/tview"
+ "github.com/senorprogrammer/wtf/wtf"
+)
+
+type Widget struct {
+ wtf.TextWidget
+
+ app *tview.Application
+ pages *tview.Pages
+ list []*List
+ idx int
+}
+
+func NewWidget(app *tview.Application, pages *tview.Pages) *Widget {
+ widget := Widget{
+ TextWidget: wtf.NewTextWidget(" Todoist ", "todoist", true),
+
+ app: app,
+ pages: pages,
+ }
+
+ todoist.Token = os.Getenv("WTF_TODOIST_TOKEN")
+ widget.list = loadProjects()
+ widget.View.SetInputCapture(widget.keyboardIntercept)
+
+ return &widget
+}
+
+func (w *Widget) Refresh() {
+ if w.Disabled() || len(w.list) == 0 {
+ return
+ }
+
+ w.UpdateRefreshedAt()
+ w.display()
+}
+
+func (w *Widget) Next() {
+ w.idx = w.idx + 1
+ if w.idx == len(w.list) {
+ w.idx = 0
+ }
+
+ w.display()
+}
+
+func (w *Widget) Prev() {
+ w.idx = w.idx - 1
+ if w.idx < 0 {
+ w.idx = len(w.list) - 1
+ }
+
+ w.display()
+}
+
+func (w *Widget) Down() {
+ w.list[w.idx].down()
+ w.display()
+}
+
+func (w *Widget) UP() {
+ w.list[w.idx].up()
+ w.display()
+}
+
+func (w *Widget) Close() {
+ w.list[w.idx].close()
+ if w.list[w.idx].isLast() {
+ w.UP()
+ return
+ }
+ w.Down()
+}
+
+func (w *Widget) Delete() {
+ w.list[w.idx].close()
+ if w.list[w.idx].isLast() {
+ w.UP()
+ return
+ }
+ w.Down()
+}
+
+func loadProjects() []*List {
+ lists := []*List{}
+ for _, id := range wtf.Config.UList("wtf.mods.todoist.projects") {
+ list := NewList(id.(int))
+ lists = append(lists, list)
+ }
+
+ return lists
+}
+
+func fromVim(event *tcell.EventKey) tcell.Key {
+ switch string(event.Rune()) {
+ case "h":
+ return tcell.KeyLeft
+ case "l":
+ return tcell.KeyRight
+ case "k":
+ return tcell.KeyUp
+ case "j":
+ return tcell.KeyDown
+ }
+ return event.Key()
+}
diff --git a/vendor/github.com/darkSasori/todoist/.gitignore b/vendor/github.com/darkSasori/todoist/.gitignore
new file mode 100644
index 00000000..9a990fe4
--- /dev/null
+++ b/vendor/github.com/darkSasori/todoist/.gitignore
@@ -0,0 +1,2 @@
+*.out
+vendor/*
diff --git a/vendor/github.com/darkSasori/todoist/.travis.yml b/vendor/github.com/darkSasori/todoist/.travis.yml
new file mode 100644
index 00000000..0052d33c
--- /dev/null
+++ b/vendor/github.com/darkSasori/todoist/.travis.yml
@@ -0,0 +1,19 @@
+language: go
+
+notifications:
+ email: false
+
+go:
+ - "1.10.3"
+ - master
+
+before_install:
+ - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
+ - go get -u github.com/golang/lint/golint
+
+install:
+ - dep ensure
+
+script:
+ - golint -set_exit_status
+ - go test -v
diff --git a/vendor/github.com/darkSasori/todoist/Gopkg.lock b/vendor/github.com/darkSasori/todoist/Gopkg.lock
new file mode 100644
index 00000000..99656b15
--- /dev/null
+++ b/vendor/github.com/darkSasori/todoist/Gopkg.lock
@@ -0,0 +1,21 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+ branch = "master"
+ name = "github.com/darkSasori/todoist"
+ packages = ["."]
+ revision = "ab3a9f0b9d551ab4e57f1caae9bb20d5aa51ec10"
+
+[[projects]]
+ branch = "v1"
+ name = "gopkg.in/jarcoal/httpmock.v1"
+ packages = ["."]
+ revision = "16f9a43967d613f0adc2000f0094a17b9f6c4c20"
+
+[solve-meta]
+ analyzer-name = "dep"
+ analyzer-version = 1
+ inputs-digest = "4d0d4d6dd7b4141be119cb48281ea7c10f05d3037c0e4ac49560cf4af21917a7"
+ solver-name = "gps-cdcl"
+ solver-version = 1
diff --git a/vendor/github.com/darkSasori/todoist/Gopkg.toml b/vendor/github.com/darkSasori/todoist/Gopkg.toml
new file mode 100644
index 00000000..ec866dff
--- /dev/null
+++ b/vendor/github.com/darkSasori/todoist/Gopkg.toml
@@ -0,0 +1,34 @@
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+# name = "github.com/user/project"
+# version = "1.0.0"
+#
+# [[constraint]]
+# name = "github.com/user/project2"
+# branch = "dev"
+# source = "github.com/myfork/project2"
+#
+# [[override]]
+# name = "github.com/x/y"
+# version = "2.4.0"
+#
+# [prune]
+# non-go = false
+# go-tests = true
+# unused-packages = true
+
+
+[[constraint]]
+ branch = "v1"
+ name = "gopkg.in/jarcoal/httpmock.v1"
+
+[prune]
+ go-tests = true
+ unused-packages = true
diff --git a/vendor/github.com/darkSasori/todoist/LICENSE b/vendor/github.com/darkSasori/todoist/LICENSE
new file mode 100644
index 00000000..cc68e07a
--- /dev/null
+++ b/vendor/github.com/darkSasori/todoist/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Lineu Felipe
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/darkSasori/todoist/README.md b/vendor/github.com/darkSasori/todoist/README.md
new file mode 100644
index 00000000..8ffc3976
--- /dev/null
+++ b/vendor/github.com/darkSasori/todoist/README.md
@@ -0,0 +1,6 @@
+# todoist
+[](https://godoc.org/github.com/darkSasori/todoist)
+[](https://travis-ci.org/darkSasori/todoist)
+[](https://goreportcard.com/report/github.com/darkSasori/todoist)
+
+Unofficial todoist api implementation
diff --git a/vendor/github.com/darkSasori/todoist/projects.go b/vendor/github.com/darkSasori/todoist/projects.go
new file mode 100644
index 00000000..9493b18a
--- /dev/null
+++ b/vendor/github.com/darkSasori/todoist/projects.go
@@ -0,0 +1,149 @@
+package todoist
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+// Project is a model of todoist project entity
+type Project struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ CommentCount int `json:"comment_count"`
+ Order int `json:"order"`
+ Indent int `json:"indent"`
+}
+
+func decodeProject(body io.ReadCloser) (Project, error) {
+ defer body.Close()
+ decoder := json.NewDecoder(body)
+ var project Project
+
+ if err := decoder.Decode(&project); err != nil {
+ return Project{}, err
+ }
+ return project, nil
+}
+
+// ListProject return all projects
+//
+// Example:
+// todoist.Token = "your token"
+// projects, err := todoist.ListProject()
+// if err != nil {
+// panic(err)
+// }
+// fmt.Println(projects)
+func ListProject() ([]Project, error) {
+ res, err := makeRequest(http.MethodGet, "projects", nil)
+ if err != nil {
+ return []Project{}, err
+ }
+
+ defer res.Body.Close()
+ decoder := json.NewDecoder(res.Body)
+ var projects []Project
+
+ if err := decoder.Decode(&projects); err != nil {
+ return []Project{}, err
+ }
+
+ return projects, nil
+}
+
+// GetProject return a project by id
+//
+// Example:
+// todoist.Token = "your token"
+// project, err := todoist.GetProject(1)
+// if err != nil {
+// panic(err)
+// }
+// fmt.Println(project)
+func GetProject(id int) (Project, error) {
+ path := fmt.Sprintf("projects/%d", id)
+ res, err := makeRequest(http.MethodGet, path, nil)
+ if err != nil {
+ return Project{}, err
+ }
+
+ return decodeProject(res.Body)
+}
+
+// CreateProject create a new project with a name
+//
+// Example:
+// todoist.Token = "your token"
+// project, err := todoist.CreateProject("New Project")
+// if err != nil {
+// panic(err)
+// }
+// fmt.Println(project)
+func CreateProject(name string) (Project, error) {
+ project := struct {
+ Name string `json:"name"`
+ }{
+ name,
+ }
+
+ res, err := makeRequest(http.MethodPost, "projects", project)
+ if err != nil {
+ return Project{}, err
+ }
+
+ return decodeProject(res.Body)
+}
+
+// Delete project
+//
+// Example:
+// todoist.Token = "your token"
+// project, err := todoist.GetProject(1)
+// if err != nil {
+// panic(err)
+// }
+// err = project.Delete()
+// if err != nil {
+// panic(err)
+// }
+func (p Project) Delete() error {
+ path := fmt.Sprintf("projects/%d", p.ID)
+ _, err := makeRequest(http.MethodDelete, path, nil)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Update project
+//
+// Example:
+// todoist.Token = "your token"
+// project, err := todoist.GetProject(1)
+// if err != nil {
+// panic(err)
+// }
+// project.Name = "updated"
+// err = project.Update()
+// if err != nil {
+// panic(err)
+// }
+// fmt.Println(project)
+func (p Project) Update() error {
+ path := fmt.Sprintf("projects/%d", p.ID)
+ project := struct {
+ Name string `json:"name"`
+ }{
+ p.Name,
+ }
+
+ _, err := makeRequest(http.MethodPost, path, project)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/darkSasori/todoist/task.go b/vendor/github.com/darkSasori/todoist/task.go
new file mode 100644
index 00000000..f2c0ce06
--- /dev/null
+++ b/vendor/github.com/darkSasori/todoist/task.go
@@ -0,0 +1,158 @@
+package todoist
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+// Task is a model of todoist project entity
+type Task struct {
+ ID int `json:"id"`
+ CommentCount int `json:"comment_count"`
+ Completed bool `json:"completed"`
+ Content string `json:"content"`
+ Indent int `json:"indent"`
+ LabelIDs []int `json:"label_ids"`
+ Order int `json:"order"`
+ Priority int `json:"priority"`
+ ProjectID int `json:"project_id"`
+ Due Due `json:"due"`
+}
+
+// Due is a model of todoist project entity
+type Due struct {
+ String string `json:"string"`
+ Date string `json:"date"`
+ Datetime CustomTime `json:"datetime"`
+ Timezone string `json:"timezone"`
+}
+
+func (t Task) taskSave() taskSave {
+ return taskSave{
+ t.Content,
+ t.ProjectID,
+ t.Order,
+ t.LabelIDs,
+ t.Priority,
+ t.Due.String,
+ t.Due.Datetime,
+ "en",
+ }
+}
+
+func decodeTask(body io.ReadCloser) (Task, error) {
+ defer body.Close()
+ decoder := json.NewDecoder(body)
+ var task Task
+
+ if err := decoder.Decode(&task); err != nil {
+ return Task{}, err
+ }
+ return task, nil
+}
+
+// QueryParam is a map[string]string to build http query
+type QueryParam map[string]string
+
+func (qp QueryParam) String() string {
+ if len(qp) == 0 {
+ return ""
+ }
+
+ ret := "?"
+ for key, value := range qp {
+ if ret != "?" {
+ ret = ret + "&"
+ }
+ ret = ret + key + "=" + value
+ }
+
+ return ret
+}
+
+// ListTask return all task, you can filter using QueryParam
+// See documentation: https://developer.todoist.com/rest/v8/#get-tasks
+func ListTask(qp QueryParam) ([]Task, error) {
+ path := fmt.Sprintf("tasks%s", qp)
+ res, err := makeRequest(http.MethodGet, path, nil)
+ if err != nil {
+ return []Task{}, err
+ }
+
+ defer res.Body.Close()
+ decoder := json.NewDecoder(res.Body)
+ var tasks []Task
+
+ if err := decoder.Decode(&tasks); err != nil {
+ return []Task{}, err
+ }
+
+ return tasks, nil
+}
+
+// GetTask return a task by id
+func GetTask(id int) (Task, error) {
+ path := fmt.Sprintf("tasks/%d", id)
+ res, err := makeRequest(http.MethodGet, path, nil)
+ if err != nil {
+ return Task{}, err
+ }
+
+ return decodeTask(res.Body)
+}
+
+// CreateTask create a new task
+func CreateTask(task Task) (Task, error) {
+ res, err := makeRequest(http.MethodPost, "tasks", task.taskSave())
+ if err != nil {
+ return Task{}, err
+ }
+
+ return decodeTask(res.Body)
+}
+
+// Delete remove a task
+func (t Task) Delete() error {
+ path := fmt.Sprintf("tasks/%d", t.ID)
+ _, err := makeRequest(http.MethodDelete, path, nil)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Update a task
+func (t Task) Update() error {
+ path := fmt.Sprintf("tasks/%d", t.ID)
+ _, err := makeRequest(http.MethodPost, path, t.taskSave())
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Close mask task as done
+func (t Task) Close() error {
+ path := fmt.Sprintf("tasks/%d/close", t.ID)
+ _, err := makeRequest(http.MethodPost, path, nil)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Reopen a task
+func (t Task) Reopen() error {
+ path := fmt.Sprintf("tasks/%d/reopen", t.ID)
+ _, err := makeRequest(http.MethodPost, path, nil)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/darkSasori/todoist/todoist.go b/vendor/github.com/darkSasori/todoist/todoist.go
new file mode 100644
index 00000000..d487426c
--- /dev/null
+++ b/vendor/github.com/darkSasori/todoist/todoist.go
@@ -0,0 +1,145 @@
+package todoist
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "strings"
+ "time"
+)
+
+// Token save the personal token from todoist
+var Token string
+var todoistURL = "https://beta.todoist.com/API/v8/"
+
+func makeRequest(method, endpoint string, data interface{}) (*http.Response, error) {
+ url := todoistURL + endpoint
+ body := bytes.NewBuffer([]byte{})
+
+ if data != nil {
+ json, err := json.Marshal(data)
+ if err != nil {
+ return nil, err
+ }
+ body = bytes.NewBuffer(json)
+ }
+
+ req, err := http.NewRequest(method, url, body)
+ if err != nil {
+ return nil, err
+ }
+
+ bearer := fmt.Sprintf("Bearer %s", Token)
+ req.Header.Add("Authorization", bearer)
+
+ if data != nil {
+ req.Header.Add("Content-Type", "application/json")
+ }
+
+ client := &http.Client{}
+ res, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ if res.StatusCode >= 400 {
+ defer res.Body.Close()
+ str, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return nil, err
+ }
+ return nil, fmt.Errorf(string(str))
+ }
+
+ return res, nil
+}
+
+const ctLayout = "2006-01-02T15:04:05+00:00"
+
+// CustomTime had a custom json date format
+type CustomTime struct {
+ time.Time
+}
+
+// UnmarshalJSON convert from []byte to CustomTime
+func (ct *CustomTime) UnmarshalJSON(b []byte) (err error) {
+ s := strings.Trim(string(b), "\"")
+ if s == "null" {
+ ct.Time = time.Time{}
+ return nil
+ }
+
+ ct.Time, err = time.Parse(ctLayout, s)
+ return err
+}
+
+// MarshalJSON convert CustomTime to []byte
+func (ct CustomTime) MarshalJSON() ([]byte, error) {
+ if ct.Time.IsZero() {
+ return []byte("null"), nil
+ }
+ return []byte(`"` + ct.Time.Format(ctLayout) + `"`), nil
+}
+
+type taskSave struct {
+ Content string `json:"content"`
+ ProjectID int `json:"project_id,omitempty"`
+ Order int `json:"order,omitempty"`
+ LabelIDs []int `json:"label_ids,omitempty"`
+ Priority int `json:"priority,omitempty"`
+ DueString string `json:"due_string,omitempty"`
+ DueDateTime CustomTime `json:"due_datetime,omitempty"`
+ DueLang string `json:"due_lang,omitempty"`
+}
+
+func (ts taskSave) MarshalJSON() ([]byte, error) {
+ buffer := bytes.NewBufferString("{")
+
+ if ts.Content == "" {
+ return nil, fmt.Errorf("Content is empty")
+ }
+ buffer.WriteString(fmt.Sprintf("\"content\":\"%s\"", ts.Content))
+
+ if ts.ProjectID != 0 {
+ buffer.WriteString(fmt.Sprintf(",\"project_id\":%d", ts.ProjectID))
+ }
+
+ if ts.Order != 0 {
+ buffer.WriteString(fmt.Sprintf(",\"order\":%d", ts.Order))
+ }
+
+ if !ts.DueDateTime.IsZero() {
+ buffer.WriteString(",\"due_datetime\":")
+ json, err := json.Marshal(ts.DueDateTime)
+ if err != nil {
+ return nil, err
+ }
+ buffer.Write(json)
+ }
+
+ if len(ts.LabelIDs) != 0 {
+ buffer.WriteString(",\"label_ids\":")
+ json, err := json.Marshal(ts.LabelIDs)
+ if err != nil {
+ return nil, err
+ }
+ buffer.Write(json)
+ }
+
+ if ts.Priority != 0 {
+ buffer.WriteString(fmt.Sprintf(",\"priority\":%d", ts.Priority))
+ }
+
+ if ts.DueString != "" {
+ buffer.WriteString(fmt.Sprintf(",\"due_string\":\"%s\"", ts.DueString))
+ }
+
+ if ts.DueLang != "" {
+ buffer.WriteString(fmt.Sprintf(",\"due_lang\":\"%s\"", ts.DueLang))
+ }
+
+ buffer.WriteString("}")
+ return buffer.Bytes(), nil
+}
diff --git a/wtf.go b/wtf.go
index 79b2a6d8..c7709776 100644
--- a/wtf.go
+++ b/wtf.go
@@ -39,6 +39,7 @@ import (
"github.com/senorprogrammer/wtf/system"
"github.com/senorprogrammer/wtf/textfile"
"github.com/senorprogrammer/wtf/todo"
+ "github.com/senorprogrammer/wtf/todoist"
"github.com/senorprogrammer/wtf/trello"
"github.com/senorprogrammer/wtf/weatherservices/prettyweather"
"github.com/senorprogrammer/wtf/weatherservices/weather"
@@ -218,6 +219,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) {
Widgets = append(Widgets, textfile.NewWidget(app, pages))
case "todo":
Widgets = append(Widgets, todo.NewWidget(app, pages))
+ case "todoist":
+ Widgets = append(Widgets, todoist.NewWidget(app, pages))
case "trello":
Widgets = append(Widgets, trello.NewWidget())
case "weather":
@@ -233,7 +236,6 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) {
if enabled := Config.UBool("wtf.mods."+mod+".enabled", false); enabled {
addWidget(app, pages, mod)
}
-
}
}