diff --git a/README.md b/README.md index 5113504..39a2769 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ B open all bookmarks in the current task's context c update context for a task ctrl+d archive/unarchive task ctrl+x delete task +y copy selected task's context to system clipboard v toggle between compact and spacious view Active Tasks List @@ -214,6 +215,7 @@ K move task one position up Task Details Pane h/l move backwards/forwards when in the task details view +y copy selected task's context to system clipboard B open all bookmarks in the current task's context Context Bookmarks List diff --git a/cmd/guide.go b/cmd/guide.go index 819238d..66eb63a 100644 --- a/cmd/guide.go +++ b/cmd/guide.go @@ -177,6 +177,8 @@ order). You can override this behavior by passing the "editor" flag to omm, like Go ahead, press "c". Try changing the text, and then save the file. This context text should get updated accordingly. + +Once saved, you can also copy a tasks's context to your system clipboard by pressing "y". `, true, }, diff --git a/go.mod b/go.mod index c025be5..ad3e153 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/dhth/omm go 1.22.5 require ( + github.com/atotto/clipboard v0.1.4 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.26.6 github.com/charmbracelet/lipgloss v0.11.0 @@ -15,7 +16,6 @@ require ( ) require ( - github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/x/ansi v0.1.2 // indirect github.com/charmbracelet/x/input v0.1.2 // indirect diff --git a/internal/ui/cmds.go b/internal/ui/cmds.go index 61a62ba..a06785f 100644 --- a/internal/ui/cmds.go +++ b/internal/ui/cmds.go @@ -7,6 +7,7 @@ import ( "database/sql" + "github.com/atotto/clipboard" tea "github.com/charmbracelet/bubbletea" pers "github.com/dhth/omm/internal/persistence" "github.com/dhth/omm/internal/types" @@ -115,3 +116,10 @@ func openURLsDarwin(urls []string) tea.Cmd { return urlsOpenedDarwinMsg{urls, err} }) } + +func copyContextToClipboard(context string) tea.Cmd { + return func() tea.Msg { + err := clipboard.WriteAll(context) + return contextWrittenToCBMsg{err} + } +} diff --git a/internal/ui/help.go b/internal/ui/help.go index 1bbd302..db331a2 100644 --- a/internal/ui/help.go +++ b/internal/ui/help.go @@ -54,6 +54,7 @@ B open all bookmarks in the current task's context c update context for a task ctrl+d archive/unarchive task ctrl+x delete task +y copy selected task's context to system clipboard v toggle between compact and spacious view`), helpSubHeadingStyle.Render("Active Tasks List"), helpSectionStyle.Render(`q/esc/ctrl+c quit @@ -68,6 +69,7 @@ J move task one position down K move task one position up`), helpSubHeadingStyle.Render("Task Details Pane"), helpSectionStyle.Render(`h/l move backwards/forwards when in the task details view +y copy current task's context to system clipboard B open all bookmarks in the current task's context`), helpSubHeadingStyle.Render("Context Bookmarks List"), helpSectionStyle.Render(`⏎ open URL in browser`), diff --git a/internal/ui/model.go b/internal/ui/model.go index 4c8e7c8..3ec2242 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -115,11 +115,12 @@ type model struct { helpVPReady bool quitting bool showHelpIndicator bool + successMsg string errorMsg string taskInput textinput.Model activeView activeView lastActiveView activeView - lastActiveList taskListType + activeTaskList taskListType tlTitleStyle lipgloss.Style atlTitleStyle lipgloss.Style tlSelStyle lipgloss.Style diff --git a/internal/ui/msgs.go b/internal/ui/msgs.go index d80c892..a568734 100644 --- a/internal/ui/msgs.go +++ b/internal/ui/msgs.go @@ -75,3 +75,7 @@ type urlsOpenedDarwinMsg struct { urls []string err error } + +type contextWrittenToCBMsg struct { + err error +} diff --git a/internal/ui/styles.go b/internal/ui/styles.go index 2597cc9..95b32df 100644 --- a/internal/ui/styles.go +++ b/internal/ui/styles.go @@ -22,7 +22,8 @@ const ( helpTitleColor = "#83a598" helpHeaderColor = "#83a598" helpSectionColor = "#bdae93" - statusBarColor = "#fb4934" + sBSuccessMsgColor = "#d3869b" + sBErrMsgColor = "#fb4934" footerColor = "#928374" ) @@ -58,9 +59,14 @@ var ( PaddingBottom(1). PaddingLeft(2) - statusBarStyle = lipgloss.NewStyle(). - PaddingLeft(2). - Foreground(lipgloss.Color(statusBarColor)) + statusBarMsgStyle = lipgloss.NewStyle(). + PaddingLeft(2) + + sBErrMsgStyle = statusBarMsgStyle. + Foreground(lipgloss.Color(sBErrMsgColor)) + + sBSuccessMsgStyle = statusBarMsgStyle. + Foreground(lipgloss.Color(sBSuccessMsgColor)) taskDetailsStyle = lipgloss.NewStyle(). PaddingLeft(2). diff --git a/internal/ui/update.go b/internal/ui/update.go index 217c5ce..466c748 100644 --- a/internal/ui/update.go +++ b/internal/ui/update.go @@ -25,6 +25,7 @@ const ( func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd var cmds []tea.Cmd + m.successMsg = "" m.errorMsg = "" if m.activeView == taskEntryView { @@ -33,14 +34,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch keypress := msg.String(); keypress { case "esc", "ctrl+c": m.activeView = taskListView - m.lastActiveList = activeTasks + m.activeTaskList = activeTasks case "enter": taskSummary := m.taskInput.Value() taskSummary = strings.TrimSpace(taskSummary) if taskSummary == "" { m.activeView = taskListView - m.lastActiveList = activeTasks + m.activeTaskList = activeTasks break } @@ -51,13 +52,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) m.taskInput.Reset() m.activeView = taskListView - m.lastActiveList = activeTasks + m.activeTaskList = activeTasks case taskUpdateSummary: cmd = updateTaskSummary(m.db, m.taskIndex, m.taskId, taskSummary) cmds = append(cmds, cmd) m.taskInput.Reset() m.activeView = taskListView - m.lastActiveList = activeTasks + m.activeTaskList = activeTasks } } } @@ -71,7 +72,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: w, h := listStyle.GetFrameSize() _, h2 := headerStyle.GetFrameSize() - _, h3 := statusBarStyle.GetFrameSize() + _, h3 := statusBarMsgStyle.GetFrameSize() m.terminalWidth = msg.Width m.terminalHeight = msg.Height m.taskList.SetWidth(msg.Width - w) @@ -143,7 +144,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.activeView == archivedTaskListView { m.activeView = taskListView - m.lastActiveList = activeTasks + m.activeTaskList = activeTasks m.lastActiveView = av break } @@ -152,9 +153,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.activeView = m.lastActiveView switch m.activeView { case taskListView: - m.lastActiveList = activeTasks + m.activeTaskList = activeTasks case archivedTaskListView: - m.lastActiveList = archivedTasks + m.activeTaskList = archivedTasks } break } @@ -182,10 +183,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch m.activeView { case taskListView: m.activeView = archivedTaskListView - m.lastActiveList = archivedTasks + m.activeTaskList = archivedTasks case archivedTaskListView: m.activeView = taskListView - m.lastActiveList = activeTasks + m.activeTaskList = activeTasks } case "2", "3", "4", "5", "6", "7", "8", "9": @@ -510,7 +511,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var ok bool var index int - switch m.lastActiveList { + switch m.activeTaskList { case activeTasks: t, ok = m.taskList.SelectedItem().(types.Task) if !ok { @@ -557,7 +558,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var taskList list.Model var archivedTaskList list.Model _, h2 := headerStyle.GetFrameSize() - _, h3 := statusBarStyle.GetFrameSize() + _, h3 := statusBarMsgStyle.GetFrameSize() tlIndex := m.taskList.Index() atlIndex := m.archivedTaskList.Index() @@ -654,7 +655,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } _, h2 := headerStyle.GetFrameSize() - _, h3 := statusBarStyle.GetFrameSize() + _, h3 := statusBarMsgStyle.GetFrameSize() var contextHeight int if m.cfg.ListDensity == Compact { @@ -693,9 +694,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch m.activeView { case taskListView: - m.lastActiveList = activeTasks + m.activeTaskList = activeTasks default: - m.lastActiveList = archivedTasks + m.activeTaskList = archivedTasks } m.lastActiveView = m.activeView m.activeView = taskDetailsView @@ -707,7 +708,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var t types.Task var ok bool - switch m.lastActiveList { + switch m.activeTaskList { case activeTasks: m.taskList.CursorUp() t, ok = m.taskList.SelectedItem().(types.Task) @@ -730,7 +731,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var t types.Task var ok bool - switch m.lastActiveList { + switch m.activeTaskList { case activeTasks: m.taskList.CursorDown() t, ok = m.taskList.SelectedItem().(types.Task) @@ -772,9 +773,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.contextBMList.SetItems(bmItems) switch m.activeView { case taskListView: - m.lastActiveList = activeTasks + m.activeTaskList = activeTasks case archivedTaskListView: - m.lastActiveList = archivedTasks + m.activeTaskList = archivedTasks } m.lastActiveView = m.activeView m.activeView = contextBookmarksView @@ -807,6 +808,39 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { for _, url := range urls { cmds = append(cmds, openURL(url)) } + + case "y": + if m.activeView != taskListView && m.activeView != archivedTaskListView && m.activeView != taskDetailsView { + break + } + + var t types.Task + var ok bool + + switch m.activeView { + case taskListView: + t, ok = m.taskList.SelectedItem().(types.Task) + case archivedTaskListView: + t, ok = m.archivedTaskList.SelectedItem().(types.Task) + case taskDetailsView: + switch m.activeTaskList { + case activeTasks: + t, ok = m.taskList.SelectedItem().(types.Task) + case archivedTasks: + t, ok = m.archivedTaskList.SelectedItem().(types.Task) + } + } + + if !ok { + break + } + + if t.Context == nil { + m.errorMsg = "There's no context to copy" + break + } + + cmds = append(cmds, copyContextToClipboard(*t.Context)) } case HideHelpMsg: @@ -996,7 +1030,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { break } - cmds = append(cmds, updateTaskContext(m.db, msg.taskIndex, msg.taskId, string(context), m.lastActiveList)) + cmds = append(cmds, updateTaskContext(m.db, msg.taskIndex, msg.taskId, string(context), m.activeTaskList)) case urlOpenedMsg: if msg.err != nil { m.errorMsg = fmt.Sprintf("Error opening url: %s", msg.err) @@ -1005,6 +1039,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.err != nil { m.errorMsg = fmt.Sprintf("Error opening urls: %s", msg.err) } + + case contextWrittenToCBMsg: + if msg.err != nil { + m.errorMsg = fmt.Sprintf("Couldn't copy context to clipboard: %s", msg.err) + } else { + m.successMsg = "Context copied to clipboard!" + } } if m.cfg.ListDensity == Compact { @@ -1127,7 +1168,7 @@ func (m model) getContextUrls() ([]string, bool) { case archivedTaskListView: t, ok = m.archivedTaskList.SelectedItem().(types.Task) case taskDetailsView: - switch m.lastActiveList { + switch m.activeTaskList { case activeTasks: t, ok = m.taskList.SelectedItem().(types.Task) case archivedTasks: diff --git a/internal/ui/view.go b/internal/ui/view.go index ed64ba2..646ab73 100644 --- a/internal/ui/view.go +++ b/internal/ui/view.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/charmbracelet/lipgloss" + "github.com/dhth/omm/internal/utils" ) var ( @@ -22,8 +23,15 @@ func (m model) View() string { var helpMsg string var listEmpty bool - if m.errorMsg != "" { - statusBar = m.errorMsg + if m.errorMsg != "" && m.successMsg != "" { + statusBar = fmt.Sprintf("%s%s", + sBErrMsgStyle.Render(utils.Trim(m.errorMsg, (m.terminalWidth/2)-3)), + sBSuccessMsgStyle.Render(utils.Trim(m.successMsg, (m.terminalWidth/2)-3)), + ) + } else if m.errorMsg != "" { + statusBar = sBErrMsgStyle.Render(m.errorMsg) + } else if m.successMsg != "" { + statusBar = sBSuccessMsgStyle.Render(m.successMsg) } if m.showHelpIndicator && (m.activeView == taskListView || m.activeView == archivedTaskListView) { @@ -117,7 +125,7 @@ func (m model) View() string { context = taskDetailsStyle.Render(m.taskDetailsVP.View()) } - return lipgloss.JoinVertical(lipgloss.Left, headerStyle.Render(header), context, statusBarStyle.Render(statusBar)) + return lipgloss.JoinVertical(lipgloss.Left, headerStyle.Render(header), context, statusBar) case contextBookmarksView: header = fmt.Sprintf("%s%s", contextBMTitleStyle.Render("Context Bookmarks"), helpMsg) @@ -150,7 +158,7 @@ func (m model) View() string { components = append(components, context) } - components = append(components, statusBarStyle.Render(statusBar)) + components = append(components, statusBar) return lipgloss.JoinVertical(lipgloss.Left, components...)