Skip to content

Commit 1cb95b3

Browse files
committed
Prepared for first release
- Creating db file in home dir or DB_FILE env variable - Updated README with usages details - Added GIF screencast
1 parent 89624ed commit 1cb95b3

File tree

15 files changed

+373
-114
lines changed

15 files changed

+373
-114
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
builds/
1617
/geek-life
1718
/geek-life.db

README.md

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,135 @@
1-
# geek-task
2-
Todo List Manager for Geeks
1+
geek-life - The CLI Task Manager for Geeks :technologist:
2+
=========
3+
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5+
[![Go Report Card](https://goreportcard.com/badge/github.com/ajaxray/geek-life)](https://goreportcard.com/report/github.com/ajaxray/geek-life)
6+
7+
8+
:superhero: Command Line hero?
9+
:computer: Live with the dark terminal?
10+
:memo: Think in Markdown?
11+
12+
**Finally!** A full featured task manager for YOU!
13+
14+
![Geek-life overview](screens/geek-life_v1.gif "Geek-life overview")
15+
16+
### Highlights
17+
18+
- For ninjas - do things faster with keyboard shortcuts
19+
- Markdown lovers, feel at :house:! You'll see markdown everywhere.
20+
- Full featured (almost) - Projects, Tasks, due-dates, task notes...
21+
- A <4MB app that takes <1% CPU and ~7MB memory <sup>1</sup> - how much lighter you can think?
22+
- Task note editor with markdown syntax highlighting<sup>2</sup>
23+
- Full mouse support
24+
25+
### Roadmap
26+
- [x] Create Project
27+
- [x] Delete Project
28+
- [ ] Edit Project
29+
- [x] Create Task (under project)
30+
- [x] Set Task due date (as `dd-mm-yyyy`) with shortcut
31+
- [x] Set Task due date with quick input buttons (today, +1 day, -1 day)
32+
- [x] Tasklist items should indicate status (done, pending, overdue) using colors
33+
- [x] Shortcut for Adding new Project and Task
34+
- [x] Global shortcuts for jumping to Projects or Tasks panel anytime
35+
- [x] Cleanup all completed tasks of project
36+
- [x] Task note editor should syntax highlight (markdown) and line numbers
37+
- [x] Status bar for common shortcuts
38+
- [x] Status bar should display success/error message of actions
39+
- [x] Status bar may display quick tips based on focused element
40+
- [ ] Dynamic lists
41+
- Today - Due Today and overdue
42+
- Upcoming - Due in one week
43+
- Someday - No due date
44+
- [ ] [Havitica](https://habitica.com/)<sup>3</sup> integration - Use it as Habitica client or use Habitica for cloud backup
45+
- [ ] Time tracking
46+
47+
### Ready for action (installing and running)
48+
49+
It's just a single binary file, **no external dependencies**.
50+
Just download the appropriate version of [executable from latest release](https://github.com/ajaxray/geek-life/releases) for your OS.
51+
Then rename and give it permission to execute. For example
52+
```bash
53+
mv geek-life_linux-amd64 geek-life
54+
sudo chmod +x geek-life
55+
```
56+
57+
If you want to install it globally (run from any directory of your system), put it in your systems $PATH directory.
58+
```bash
59+
sudo mv geek-life /usr/local/bin/geek-life
60+
```
61+
Done!
62+
63+
## Keyboard shortcuts
64+
65+
Some shortcuts are global, some are contextual.
66+
Contextual shortcuts will be applied according to focused pane/element.
67+
You'll see a currently focused pane bordered with double line.
68+
69+
In case writing in a text input (e,g, new project/task, due date), you have to `Enter` to submit/save.
70+
71+
| Context | Shortcut | Action |
72+
|---|:---:|---|
73+
| Global | `p` | Go to Project list |
74+
| Global | `t` | Go to Task list |
75+
| Projects | `n` | New Project |
76+
| Tasks | `n` | New Task |
77+
| Tasks | `Esc` | Go back to Projects Pane |
78+
| Task Detail | `Esc` | Go back to Tasks Pane |
79+
| Task Detail | `Space` | Toggle task as done/pending |
80+
| Task Detail | `d` | Set Due date |
81+
| Task Detail | ``/`` | Scroll Up/Down the note editor |
82+
| Task Detail | `e` | Activate note editor for modification |
83+
| Active Note Editor | `Esc` | Deactivate note editor and save content |
84+
85+
*Tips about using shortcuts efficiently:*
86+
87+
- `Esc` will bring you a step back - to previous pane in most cases.
88+
- When you're in Project or Task list, use ``/`` to navigate the list.
89+
- When you're in Project or Task list `Enter` will load currently selected Project/Task.
90+
- After creating new Project, focus will automatically move to Tasks. Start adding tasks immediately by pressing `n`.
91+
- After creating new Task, focus will stay in "new task" input. So that you can add tasks quickly one after another.
92+
- After creating new Task, Press `Esc` when you're done creating tasks.
93+
94+
## Building blocks
95+
96+
- Made with :love: and [golang](https://golang.org/) 1.14 *(you don't need golang to run it)*
97+
- Designed with [tview](https://github.com/rivo/tview) - interactive widgets for terminal-based UI
98+
- Task Note editor made with [femto](https://github.com/pgavlin/femto)
99+
- Datastore is [storm](https://github.com/asdine/storm) - a powerful toolkit for [BoltDB](https://github.com/etcd-io/bbolt)
100+
101+
### Contribute
102+
103+
If you fix a bug or want to add/improve a feature,
104+
and it's alligned with the focus (merging with ease) of this tool,
105+
I will be glad to accept your PR. :)
106+
107+
## You may ask...
108+
109+
#### Where is the data stored? Can I change the location?
110+
111+
By default, it will try to create a db file in you home directory.
112+
113+
But as a geek, you may try to put it different location (e,g, in you dropbox for syncing).
114+
In that case, just mention `DB_FILE` as an environment variable.
115+
116+
```bash
117+
DB_FILE=~/dropbox/geek-life/default.db geek-life
118+
```
119+
120+
#### How can I suggest a feature?
121+
122+
Just [post an issue](https://github.com/ajaxray/geek-life/issues/new) describing your desired feature/enhancement
123+
and select `feature` label.
124+
125+
Also, incomplete features in the current roadmap will be found in issue list.
126+
You may :thumbsup: issues if you want to increase priority of a feature.
127+
128+
---
129+
### Footnotes
130+
1. In my Macbook Air, 1.6 GHz Dual-Core Intel Core i5, RAM: 8 GB 1600 MHz DDR3
131+
2. Use [monakai](https://github.com/sickill/vim-monokai) color scheme for markdown syntax
132+
3. Habitica is a free habit and productivity app that treats your real life like a game
133+
134+
---
135+
> "This is the Book about which there is no doubt, a guidance for those conscious of Allah" - [Al-Quran](http://quran.com)

app/cli.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,22 @@ import (
1414
)
1515

1616
var (
17-
app *tview.Application
18-
newProject, newTask *tview.InputField
19-
projectList, taskList *tview.List
20-
projectPane, taskPane, detailPane *tview.Flex
21-
layout, contents *tview.Flex
22-
statusBar *tview.Pages
23-
message *tview.TextView
24-
shortcutsPage, messagePage string = "shortcuts", "message"
17+
app *tview.Application
18+
newProject, newTask *tview.InputField
19+
projectList, taskList *tview.List
20+
projectPane, projectDetailPane *tview.Flex
21+
taskPane, taskDetailPane *tview.Flex
22+
layout, contents *tview.Flex
23+
statusBar *tview.Pages
24+
message *tview.TextView
25+
shortcutsPage, messagePage string = "shortcuts", "message"
2526

2627
db *storm.DB
2728
projectRepo repository.ProjectRepository
2829
taskRepo repository.TaskRepository
2930

3031
projects []model.Project
31-
currentProject model.Project
32+
currentProject *model.Project
3233
)
3334

3435
func main() {
@@ -40,36 +41,36 @@ func main() {
4041
projectRepo = repo.NewProjectRepository(db)
4142
taskRepo = repo.NewTaskRepository(db)
4243

43-
titleText := tview.NewTextView().SetText("[lime::b]Geek-life [::-]- life management for geeks!").SetDynamicColors(true)
44+
titleText := tview.NewTextView().SetText("[lime::b]Geek-life [::-]- Task Manager for geeks!").SetDynamicColors(true)
4445
cloudStatus := tview.NewTextView().SetText("[::d]Cloud Sync: off").SetTextAlign(tview.AlignRight).SetDynamicColors(true)
4546

4647
titleBar := tview.NewFlex().
4748
AddItem(titleText, 0, 2, false).
4849
AddItem(cloudStatus, 0, 1, false)
4950

5051
prepareProjectPane()
52+
prepareProjectDetail()
5153
prepareTaskPane()
5254
prepareStatusBar()
5355
prepareDetailPane()
5456

5557
contents = tview.NewFlex().
5658
AddItem(projectPane, 25, 1, true).
5759
AddItem(taskPane, 0, 2, false)
58-
//AddItem(detailPane, 0, 3, true)
5960

6061
layout = tview.NewFlex().SetDirection(tview.FlexRow).
6162
AddItem(titleBar, 2, 1, false).
6263
AddItem(contents, 0, 2, true).
6364
AddItem(statusBar, 1, 1, false)
6465

65-
setKeyboardShortcuts(projectPane, taskPane)
66+
setKeyboardShortcuts()
6667

6768
if err := app.SetRoot(layout, true).EnableMouse(true).Run(); err != nil {
6869
panic(err)
6970
}
7071
}
7172

72-
func setKeyboardShortcuts(projectPane *tview.Flex, taskPane *tview.Flex) *tview.Application {
73+
func setKeyboardShortcuts() *tview.Application {
7374
return app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
7475
if ignoreKeyEvt() {
7576
return event
@@ -81,7 +82,7 @@ func setKeyboardShortcuts(projectPane *tview.Flex, taskPane *tview.Flex) *tview.
8182
event = handleProjectPaneShortcuts(event)
8283
case taskPane.HasFocus():
8384
event = handleTaskPaneShortcuts(event)
84-
case detailPane.HasFocus():
85+
case taskDetailPane.HasFocus():
8586
event = handleDetailPaneShortcuts(event)
8687
}
8788

@@ -110,9 +111,9 @@ func prepareStatusBar() {
110111
tview.NewGrid().
111112
SetColumns(0, 0, 0, 0).
112113
SetRows(0).
113-
AddItem(tview.NewTextView().SetText("Shortcuts: Alt+.(dot)"), 0, 0, 1, 1, 0, 0, false).
114-
AddItem(tview.NewTextView().SetText("New Project: n").SetTextAlign(tview.AlignCenter), 0, 1, 1, 1, 0, 0, false).
115-
AddItem(tview.NewTextView().SetText("New Task: t").SetTextAlign(tview.AlignCenter), 0, 2, 1, 1, 0, 0, false).
114+
AddItem(tview.NewTextView().SetText("Navigate List: ↓/↑"), 0, 0, 1, 1, 0, 0, false).
115+
AddItem(tview.NewTextView().SetText("New Task/Project: n").SetTextAlign(tview.AlignCenter), 0, 1, 1, 1, 0, 0, false).
116+
AddItem(tview.NewTextView().SetText("Step back: Esc").SetTextAlign(tview.AlignCenter), 0, 2, 1, 1, 0, 0, false).
116117
AddItem(tview.NewTextView().SetText("Quit: Ctrl+C").SetTextAlign(tview.AlignRight), 0, 3, 1, 1, 0, 0, false),
117118
true,
118119
true,

app/project_detail.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/gdamore/tcell"
7+
"github.com/rivo/tview"
8+
)
9+
10+
func prepareProjectDetail() {
11+
deleteBtn := makeButton("Delete Project", deleteCurrentProject)
12+
clearBtn := makeButton("Clear Completed Tasks", clearCompletedTasks)
13+
14+
deleteBtn.SetBackgroundColor(tcell.ColorRed)
15+
projectDetailPane = tview.NewFlex().SetDirection(tview.FlexRow).
16+
// AddItem(activeProjectName, 1, 1, false).
17+
// AddItem(makeHorizontalLine(tcell.RuneS3, tcell.ColorGray), 1, 1, false).
18+
AddItem(deleteBtn, 3, 1, false).
19+
AddItem(blankCell, 1, 1, false).
20+
AddItem(clearBtn, 3, 1, false).
21+
AddItem(blankCell, 0, 1, false)
22+
23+
projectDetailPane.SetBorder(true).SetTitle("[::u]A[::-]ctions")
24+
}
25+
26+
func deleteCurrentProject() {
27+
if currentProject != nil && projectRepo.Delete(currentProject) == nil {
28+
for i, _ := range tasks {
29+
taskRepo.Delete(&tasks[i])
30+
}
31+
32+
showMessage("Removed Project: " + currentProject.Title)
33+
removeThirdCol()
34+
taskList.Clear()
35+
projectList.Clear()
36+
37+
loadProjectList()
38+
}
39+
}
40+
41+
func clearCompletedTasks() {
42+
count := 0
43+
for i, task := range tasks {
44+
if task.Completed && taskRepo.Delete(&tasks[i]) == nil {
45+
taskList.RemoveItem(i)
46+
count++
47+
}
48+
}
49+
showMessage(fmt.Sprintf("[yellow]%d tasks cleared!", count))
50+
}

app/projects.go

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,16 @@ package main
22

33
import (
44
"fmt"
5+
"strings"
56

67
"github.com/asdine/storm/v3"
78
"github.com/gdamore/tcell"
89
"github.com/rivo/tview"
910
)
1011

1112
func prepareProjectPane() {
12-
var err error
13-
projects, err = projectRepo.GetAll()
14-
if err != nil {
15-
showMessage("Could not load Projects: " + err.Error())
16-
}
17-
1813
projectList = tview.NewList().ShowSecondaryText(false)
19-
20-
for i := range projects {
21-
addProjectToList(i, false)
22-
}
14+
loadProjectList()
2315

2416
newProject = makeLightTextInput("+[New Project]").
2517
SetDoneFunc(func(key tcell.Key) {
@@ -29,7 +21,7 @@ func prepareProjectPane() {
2921
if err != nil {
3022
showMessage("[red::]Failed to create Project:" + err.Error())
3123
} else {
32-
showMessage(fmt.Sprintf("[green::]Project %s created. Press n to start adding new tasks.", newProject.GetText()))
24+
showMessage(fmt.Sprintf("[yellow::]Project %s created. Press n to start adding new tasks.", newProject.GetText()))
3325
projects = append(projects, project)
3426
addProjectToList(len(projects)-1, true)
3527
newProject.SetText("")
@@ -46,35 +38,59 @@ func prepareProjectPane() {
4638
projectPane.SetBorder(true).SetTitle("[::u]P[::-]rojects")
4739
}
4840

41+
func loadProjectList() {
42+
var err error
43+
projects, err = projectRepo.GetAll()
44+
if err != nil {
45+
showMessage("Could not load Projects: " + err.Error())
46+
return
47+
}
48+
49+
projectList.AddItem("[::d]Dynamic Lists", "", 0, nil)
50+
projectList.AddItem("[::d]"+strings.Repeat(string(tcell.RuneS3), 25), "", 0, nil)
51+
projectList.AddItem("- Today", "", 0, yetToImplement("Today's Tasks"))
52+
projectList.AddItem("- Upcoming", "", 0, yetToImplement("Upcoming Tasks"))
53+
projectList.AddItem("- No Due Date", "", 0, yetToImplement("Unscheduled Tasks"))
54+
projectList.AddItem("", "", 0, nil)
55+
projectList.AddItem("[::d]Projects", "", 0, nil)
56+
projectList.AddItem("[::d]"+strings.Repeat(string(tcell.RuneS3), 25), "", 0, nil)
57+
58+
for i := range projects {
59+
addProjectToList(i, false)
60+
}
61+
62+
projectList.SetCurrentItem(6) // Select Projects, as dynamic lists are not ready
63+
}
64+
4965
func addProjectToList(i int, selectItem bool) {
5066
// To avoid overriding of loop variables - https://www.calhoun.io/gotchas-and-common-mistakes-with-closures-in-go/
5167
projectList.AddItem("- "+projects[i].Title, "", 0, func(idx int) func() {
5268
return func() { loadProject(idx) }
5369
}(i))
5470

5571
if selectItem {
56-
projectList.SetCurrentItem(i)
72+
projectList.SetCurrentItem(projectList.GetItemCount() - 1)
5773
loadProject(i)
5874
}
5975
}
6076

6177
func loadProject(idx int) {
62-
currentProject = projects[idx]
78+
currentProject = &projects[idx]
6379
taskList.Clear()
6480
app.SetFocus(taskPane)
6581
var err error
6682

67-
if tasks, err = taskRepo.GetAllByProject(currentProject); err != nil && err != storm.ErrNotFound {
83+
if tasks, err = taskRepo.GetAllByProject(*currentProject); err != nil && err != storm.ErrNotFound {
6884
showMessage("[red::]Error: " + err.Error())
6985
}
7086

7187
for i, task := range tasks {
72-
taskList.AddItem(makeTaskListingTitle(task), "", 0, func(taskidx int) func() {
73-
return func() { loadTask(taskidx) }
74-
}(i))
88+
addTaskToList(task, i)
7589
}
7690

77-
contents.RemoveItem(detailPane)
91+
removeThirdCol()
92+
projectDetailPane.SetTitle("[::b]" + currentProject.Title)
93+
contents.AddItem(projectDetailPane, 25, 0, false)
7894
}
7995

8096
func handleProjectPaneShortcuts(event *tcell.EventKey) *tcell.EventKey {

0 commit comments

Comments
 (0)