diff --git a/ui/pager.go b/ui/pager.go index 570c36d..72defe7 100644 --- a/ui/pager.go +++ b/ui/pager.go @@ -65,7 +65,6 @@ func newStyle(fg, bg string) func(string) string { type contentRenderedMsg string type noteSavedMsg *charm.Markdown -type statusMessageTimeoutMsg struct{} type stashSuccessMsg markdown type stashErrMsg struct { err error @@ -228,19 +227,7 @@ func pagerUpdate(msg tea.Msg, m pagerModel) (pagerModel, tea.Cmd) { // Stash a local document if m.state != pagerStateStashing && m.currentDocument.markdownType == localMarkdown { m.state = pagerStateStashing - - // Create new markdown based on loaded markdown - md := m.currentDocument - md.markdownType = stashedMarkdown - t := time.Now() - md.CreatedAt = t - - // Set the note as the filename without the extension - p := md.localPath - md.Note = strings.Replace(path.Base(p), path.Ext(p), "", 1) - md.localPath = "" - - return m, stashDocument(m.cc, md) + return m, stashDocument(m.cc, m.currentDocument) } case "?": m.toggleHelp() @@ -278,8 +265,11 @@ func pagerUpdate(msg tea.Msg, m pagerModel) (pagerModel, tea.Cmd) { m.state = pagerStateStatusMessage m.statusMessageHeader = "Stashed!" m.statusMessageBody = "" + if m.statusMessageTimer != nil { + m.statusMessageTimer.Stop() + } m.statusMessageTimer = time.NewTimer(time.Second * 3) - cmds = append(cmds, waitForStatusMessageTimeout(m.statusMessageTimer)) + cmds = append(cmds, waitForStatusMessageTimeout(pagerContext, m.statusMessageTimer)) case stashErrMsg: // TODO @@ -474,6 +464,16 @@ func stashDocument(cc *charm.Client, md markdown) tea.Cmd { return stashErrMsg{err} } } + + // Turn local markdown into a stashed markdown + md.markdownType = stashedMarkdown + md.CreatedAt = time.Now() + + // Set the note as the filename without the extension + p := md.localPath + md.Note = strings.Replace(path.Base(p), path.Ext(p), "", 1) + md.localPath = "" + return func() tea.Msg { err := cc.StashMarkdown(md.Note, md.Body) if err != nil { @@ -486,13 +486,6 @@ func stashDocument(cc *charm.Client, md markdown) tea.Cmd { } } -func waitForStatusMessageTimeout(t *time.Timer) tea.Cmd { - return func() tea.Msg { - <-t.C - return statusMessageTimeoutMsg{} - } -} - // This is where the magic happens func glamourRender(m pagerModel, markdown string) (string, error) { diff --git a/ui/stash.go b/ui/stash.go index 579af21..3b23e32 100644 --- a/ui/stash.go +++ b/ui/stash.go @@ -142,6 +142,10 @@ type stashModel struct { // than we can display at a time so we can paginate locally without having // to fetch every time. page int + + showStatusMessage bool + statusMessage string + statusMessageTimer *time.Timer } func (m *stashModel) setSize(width, height int) { @@ -197,6 +201,13 @@ func (m stashModel) countMarkdowns(t markdownType) (found int) { return } +func (m *stashModel) hideStatusMessage() { + m.showStatusMessage = false + if m.statusMessageTimer != nil { + m.statusMessageTimer.Stop() + } +} + // INIT func newStashModel() stashModel { @@ -289,13 +300,33 @@ func stashUpdate(msg tea.Msg, m stashModel) (stashModel, tea.Cmd) { m.markdowns[i].Note = msg.Note } } + + // Something was stashed. Add it to the stash listing. + case stashSuccessMsg: + md := markdown(msg) + m.addMarkdowns(&md) + + m.showStatusMessage = true + m.statusMessage = "Stashed!" + if m.statusMessageTimer != nil { + m.statusMessageTimer.Stop() + } + m.statusMessageTimer = time.NewTimer(time.Second * 3) + cmds = append(cmds, waitForStatusMessageTimeout(stashContext, m.statusMessageTimer)) + + case statusMessageTimeoutMsg: + if applicationContext(msg) == stashContext { + m.hideStatusMessage() + } } switch m.state { case stashStateReady: + switch msg := msg.(type) { + // Handle keys - if msg, ok := msg.(tea.KeyMsg); ok { + case tea.KeyMsg: switch msg.String() { case "k", "up": @@ -323,6 +354,8 @@ func stashUpdate(msg tea.Msg, m stashModel) (stashModel, tea.Cmd) { // Open document case "enter": + m.hideStatusMessage() + if len(m.markdowns) == 0 { break } @@ -342,6 +375,8 @@ func stashUpdate(msg tea.Msg, m stashModel) (stashModel, tea.Cmd) { // Set note case "m": + m.hideStatusMessage() + md := m.selectedMarkdown() isUserMarkdown := md.markdownType == stashedMarkdown isSettingNote := m.state == stashStateSettingNote @@ -354,8 +389,17 @@ func stashUpdate(msg tea.Msg, m stashModel) (stashModel, tea.Cmd) { return m, textinput.Blink(m.noteInput) } + // Stash + case "s": + md := m.selectedMarkdown() + if md != nil && md.markdownType == localMarkdown { + cmds = append(cmds, stashDocument(m.cc, *md)) + } + // Prompt for deletion case "x": + m.hideStatusMessage() + isUserMarkdown := m.selectedMarkdown().markdownType == stashedMarkdown isValidState := m.state != stashStateSettingNote @@ -500,11 +544,15 @@ func stashView(m stashModel) string { } var header string - switch m.state { - case stashStatePromptDelete: - header = redFg("Delete this item? ") + faintRedFg("(y/N)") - case stashStateSettingNote: - header = yellowFg("Set the memo for this item?") + if m.showStatusMessage { + header = greenFg(m.statusMessage) + } else { + switch m.state { + case stashStatePromptDelete: + header = redFg("Delete this item? ") + faintRedFg("(y/N)") + case stashStateSettingNote: + header = yellowFg("Set the memo for this item?") + } } // Only draw the normal header if we're not using the header area for @@ -619,11 +667,13 @@ func stashHelpView(m stashModel) string { var ( h []string isStashed bool + isLocal bool ) if len(m.markdowns) > 0 { md := m.selectedMarkdown() isStashed = md != nil && md.markdownType == stashedMarkdown + isLocal = md != nil && md.markdownType == localMarkdown } if m.state == stashStateSettingNote { @@ -640,13 +690,15 @@ func stashHelpView(m stashModel) string { if m.paginator.TotalPages > 1 { h = append(h, "h/l, ←/→: page") } - if isStashed && len(m.markdowns) > 0 { + if isStashed { h = append(h, []string{"x: delete", "m: set memo"}...) + } else if isLocal { + h = append(h, "s: stash") } if m.err != nil { - h = append(h, []string{"!: errors"}...) + h = append(h, "!: errors") } - h = append(h, []string{"q: quit"}...) + h = append(h, "q: quit") } return stashHelpViewBuilder(m.terminalWidth, h...) } diff --git a/ui/ui.go b/ui/ui.go index 5cc69c9..c932ea5 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -6,6 +6,7 @@ import ( "log" "os" "strings" + "time" "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" @@ -52,13 +53,9 @@ func NewProgram(style string, cfg Config) *tea.Program { type errMsg error type newCharmClientMsg *charm.Client type sshAuthErrMsg struct{} -type keygenFailedMsg struct { - err error -} +type keygenFailedMsg struct{ err error } -func (k keygenFailedMsg) Error() string { - return k.err.Error() -} +func (k keygenFailedMsg) Error() string { return k.err.Error() } type keygenSuccessMsg struct{} type initLocalFileSearchMsg struct { @@ -70,19 +67,26 @@ type localFileSearchFinished struct{} type gotStashMsg []*charm.Markdown type stashLoadErrMsg struct{ err error } -func (s stashLoadErrMsg) Error() string { - return s.err.Error() -} +func (s stashLoadErrMsg) Error() string { return s.err.Error() } type gotNewsMsg []*charm.Markdown type newsLoadErrMsg struct{ err error } -func (s newsLoadErrMsg) Error() string { - return s.err.Error() -} +func (s newsLoadErrMsg) Error() string { return s.err.Error() } + +type statusMessageTimeoutMsg applicationContext // MODEL +// Which part of the application something appies to. Occasionally used as an +// arguments to command and messages. +type applicationContext int + +const ( + stashContext applicationContext = iota + pagerContext +) + type state int const ( @@ -335,8 +339,13 @@ func update(msg tea.Msg, mdl tea.Model) (tea.Model, tea.Cmd) { // Something was stashed. Update the stash listing. We're caching this // here, rather than in the stash update function, because stashing // may have occurred in the pager. - md := markdown(msg) - m.stash.addMarkdowns(&md) + if m.state == stateShowDocument { + newStashModel, cmd := stashUpdate(msg, m.stash) + m.stash = newStashModel + if cmd != nil { + cmds = append(cmds, cmd) + } + } } // Process children @@ -497,6 +506,13 @@ func generateSSHKeys() tea.Msg { return keygenSuccessMsg{} } +func waitForStatusMessageTimeout(appCtx applicationContext, t *time.Timer) tea.Cmd { + return func() tea.Msg { + <-t.C + return statusMessageTimeoutMsg(appCtx) + } +} + // ETC // Convert local file path to Markdown. Note that we could be doing things