Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions cmd/note_edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"strconv"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/deadpyxel/workday/cmd/ui"
"github.com/deadpyxel/workday/internal/journal"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand Down Expand Up @@ -48,6 +50,22 @@ func editNoteInCurrentDay(cmd *cobra.Command, args []string) error {
return fmt.Errorf("The index provided is not valid for the existing notes: %d", noteIdx)
}

p := tea.NewProgram(ui.NewEditNoteModel(&entries[idx]))
if _, err := p.Run(); err != nil {
return err
}

editState := ui.NewEditNoteState(&entries[idx].Notes[noteIdx])
p = tea.NewProgram(editState)
if _, err := p.Run(); err != nil {
return err
}

if !editState.Finished {
fmt.Println("Edit cancelled.")
return nil
}

entries[idx].Notes[noteIdx] = journal.Note{Contents: newNote}

err = journal.SaveEntries(entries, journalPath)
Expand Down
41 changes: 41 additions & 0 deletions cmd/ui/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ui

import (
"strings"

"github.com/charmbracelet/bubbles/textinput"
"github.com/deadpyxel/workday/internal/journal"
)

type EditNoteModel struct {
choices []journal.Note
cursor int
selected map[int]struct{}
}

func NewEditNoteModel(entry *journal.JournalEntry) *EditNoteModel {
return &EditNoteModel{choices: entry.Notes, selected: make(map[int]struct{})}
}

type EditNoteState struct {
Note *journal.Note
NewNote textinput.Model
NewTags textinput.Model
Finished bool
}

func NewEditNoteState(note *journal.Note) *EditNoteState {
contentInput := textinput.New()
contentInput.SetValue(note.Contents)
contentInput.Placeholder = "Enter note contents"

tagsInput := textinput.New()
tagsInput.SetValue(strings.Join(note.Tags, ","))
tagsInput.Placeholder = "Enter tags, comma separated"

return &EditNoteState{
Note: note,
NewNote: contentInput,
NewTags: tagsInput,
}
}
108 changes: 108 additions & 0 deletions cmd/ui/views.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package ui

import (
"fmt"

"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
)

func (m EditNoteModel) Init() tea.Cmd {
return nil
}

func (m EditNoteModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {

// Is it a key press?
case tea.KeyMsg:

// Cool, what was the actual key pressed?
switch msg.String() {

// These keys should exit the program.
case "ctrl+c", "q":
return m, tea.Quit

// The "up" and "k" keys move the cursor up
case "up", "k":
if m.cursor > 0 {
m.cursor--
}

// The "down" and "j" keys move the cursor down
case "down", "j":
if m.cursor < len(m.choices)-1 {
m.cursor++
}

// The "enter" key and the spacebar (a literal space) toggle
// the selected state for the item that the cursor is pointing at.
case "enter", " ":
_, ok := m.selected[m.cursor]
if ok {
delete(m.selected, m.cursor)
} else {
m.selected[m.cursor] = struct{}{}
}
}
}

// Return the updated model to the Bubble Tea runtime for processing.
// Note that we're not returning a command.
return m, nil
}

func (m EditNoteModel) View() string {
s := "Which Note do you want to edit?\n\n"
for i, note := range m.choices {
// Is the cursor pointing at this choice?
cursor := " " // No cursor
if m.cursor == i {
cursor = ">" // Render cursor
}

// Is this choice selected?
checked := " "
if _, ok := m.selected[i]; ok {
checked = "x"
}

// Render the row
s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, &note)
}

s += "\nPress q to quit.\n"

return s
}

func (s *EditNoteState) Init() tea.Cmd {
return textinput.Blink
}

func (s *EditNoteState) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return s, tea.Quit
case "enter":
s.Finished = true
return s, tea.Quit
}
default:
var cmd tea.Cmd
s.NewNote, cmd = s.NewNote.Update(msg)
// s.NewTags, cmd = s.NewTags.Update(msg)
return s, cmd
}
return s, nil
}

func (s *EditNoteState) View() string {
if s.Finished {
return ""
}
return fmt.Sprintf("Editing note:\n\n%s\n\nTags: %s\n\nPress Enter to save, Ctrl+C to cancel.", s.NewNote.View(), s.NewTags.View())
}
4 changes: 2 additions & 2 deletions internal/journal/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func TestCalculateTotalTime(t *testing.T) {
t.Errorf("Expected %v, but got %v", expected, result)
}
})
t.Run("When slice contains valid entries, returns expected result with no errors", func(t *testing.T) {
t.Run("When slice contains valid entries returns expected result with no errors", func(t *testing.T) {
entries := []JournalEntry{
{StartTime: time.Date(2021, time.January, 1, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2021, time.January, 1, 12, 0, 0, 0, time.UTC)},
{StartTime: time.Date(2021, time.January, 1, 14, 0, 0, 0, time.UTC), EndTime: time.Date(2021, time.January, 1, 16, 0, 0, 0, time.UTC)},
Expand All @@ -140,7 +140,7 @@ func TestCalculateTotalTime(t *testing.T) {
}
})

t.Run("When slice contains invalid entries, returns 0 with error", func(t *testing.T) {
t.Run("When slice contains invalid entries returns 0 with error", func(t *testing.T) {
entries := []JournalEntry{
{StartTime: time.Date(2021, time.January, 1, 10, 0, 0, 0, time.UTC), EndTime: time.Date(2021, time.January, 1, 9, 0, 0, 0, time.UTC)},
}
Expand Down