-
diff --git a/docs/posts/modules/jira/index.html b/docs/posts/modules/jira/index.html
index edcfcad4..7e14006c 100644
--- a/docs/posts/modules/jira/index.html
+++ b/docs/posts/modules/jira/index.html
@@ -148,7 +148,20 @@ height="0" width="0" style="display:none;visibility:hidden">
Keyboard Commands
-None.
+Key: [return]
+Action: Open the selected issue in the browser.
+
+Key: j
+Action: Select the next item in the list.
+
+Key: k
+Action: Select the previous item in the list.
+
+Key: ↓
+Action: Select the next item in the list.
+
+Key: ↑
+Action: Select the previous item in the list.
Configuration
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
+}