From f74f50c14863d5ff6e0d2e993e1749f828f3753f Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Thu, 2 Oct 2025 10:36:19 +0200 Subject: [PATCH 1/3] split cursors --- gemlog/db.go | 1 - gemlog/list.go | 3 -- main.go | 86 ++++++++++++++++++++++++++++++++++---------------- 3 files changed, 58 insertions(+), 32 deletions(-) diff --git a/gemlog/db.go b/gemlog/db.go index cc14979..e5f123f 100644 --- a/gemlog/db.go +++ b/gemlog/db.go @@ -17,7 +17,6 @@ func genBasicAuthHeader(user, password string) string { } func listGemLogs(config *Config) ([]GemlogListEntry, error) { - slog.Info("Listing gemlogs from couchdb") url := fmt.Sprintf("%s:%d/gemlog/_design/gemlog-cli/_view/list", config.CouchDB.Host, config.CouchDB.Port) req, err := http.NewRequest("GET", url, nil) if err != nil { diff --git a/gemlog/list.go b/gemlog/list.go index 17af8cf..90c7226 100644 --- a/gemlog/list.go +++ b/gemlog/list.go @@ -1,15 +1,12 @@ package gemlog import ( - "log/slog" - tea "github.com/charmbracelet/bubbletea" ) func LoadGemlogCMD(config *Config) tea.Cmd { return func() tea.Msg { logs, err := listGemLogs(config) - slog.Info("Loaded gemlogs", "count", len(logs)) if err != nil { return ErrorMsg{err} } diff --git a/main.go b/main.go index 0f0c5f1..ce5cde7 100644 --- a/main.go +++ b/main.go @@ -33,17 +33,22 @@ const ( ) type entryListPageModel struct { - entries []gemlog.GemlogListEntry - action Action + entries []gemlog.GemlogListEntry + actionToTake Action + cursor int +} + +type actionListPageModel struct { + cursor int } type uiState struct { notification string - cursor int errorTxt string - page Page - entryListPage entryListPageModel + page Page + entryListPage entryListPageModel + actionListPage actionListPageModel } type context struct { @@ -60,8 +65,12 @@ func initialModel(config *gemlog.Config) model { ui: uiState{ page: ActionList, entryListPage: entryListPageModel{ + cursor: 0, entries: []gemlog.GemlogListEntry{}, }, + actionListPage: actionListPageModel{ + cursor: 0, + }, }, context: &context{ config: config, @@ -81,31 +90,54 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.ui.notification = fmt.Sprintf("%s\n\n", string(msg)) case gemlog.GemLogsLoaded: m.ui.entryListPage.entries = msg.Logs - m.ui.cursor = 0 + m.ui.entryListPage.cursor = 0 case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "up", "k": - if m.ui.cursor > 0 { - m.ui.cursor-- + var cursor *int + switch m.ui.page { + case EntryList: + cursor = &m.ui.entryListPage.cursor + case ActionList: + cursor = &m.ui.actionListPage.cursor + } + if cursor != nil && *cursor > 0 { + (*cursor)-- } case "down", "j": - if m.ui.cursor < len(actions)-1 { - m.ui.cursor++ + var cursor *int + if m.ui.page == EntryList && m.ui.entryListPage.cursor < len(m.ui.entryListPage.entries)-1 { + cursor = &m.ui.entryListPage.cursor + } else if m.ui.page == ActionList && m.ui.actionListPage.cursor < len(actions)-1 { + cursor = &m.ui.actionListPage.cursor + } + if cursor != nil { + (*cursor)++ } case "enter", " ": - action := actions[m.ui.cursor] - switch action { - case Write: - return m, gemlog.WritePostCMD(m.context.config) - case Read: - m.ui.page = EntryList - return m, gemlog.LoadGemlogCMD(m.context.config) - case Edit: - return m, TODOCmd - case Delete: - return m, TODOCmd + if m.ui.page == ActionList { + action := actions[m.ui.actionListPage.cursor] + switch action { + case Write: + return m, gemlog.WritePostCMD(m.context.config) + case Read: + m.ui.page = EntryList + m.ui.entryListPage.cursor = 0 + m.ui.entryListPage.actionToTake = Read + return m, gemlog.LoadGemlogCMD(m.context.config) + case Edit: + m.ui.page = EntryList + m.ui.entryListPage.cursor = 0 + m.ui.entryListPage.actionToTake = Edit + return m, gemlog.LoadGemlogCMD(m.context.config) + case Delete: + m.ui.page = EntryList + m.ui.entryListPage.cursor = 0 + m.ui.entryListPage.actionToTake = Delete + return m, gemlog.LoadGemlogCMD(m.context.config) + } } } } @@ -120,14 +152,13 @@ func (m model) View() string { s := "" if m.ui.notification != "" { s += m.ui.notification - } else { - s += "Welcome to gemlog cli!\n\nWhat post action would you like to take?\n\n" } if m.ui.page == ActionList { + s += "Welcome to gemlog cli!\n\nWhat post action would you like to take?\n\n" for i, action := range actions { cursor := " " - if m.ui.cursor == i { + if m.ui.actionListPage.cursor == i { cursor = ">" } s += fmt.Sprintf("%s %s\n", cursor, action) @@ -135,11 +166,10 @@ func (m model) View() string { } if m.ui.page == EntryList { - slog.Info("rendering entry list", "count", len(m.ui.entryListPage.entries)) + s += fmt.Sprintf("Which entry would you like to %s\n\n", m.ui.entryListPage.actionToTake) for i, entry := range m.ui.entryListPage.entries { - slog.Info("entry", "value", entry) cursor := " " - if m.ui.cursor == i { + if m.ui.entryListPage.cursor == i { cursor = ">" } s += fmt.Sprintf("%s %s : %s\n", cursor, entry.Date, entry.Slug) @@ -160,7 +190,7 @@ func main() { defer f.Close() slog.Info("Starting gemlog cli") config, err := gemlog.LoadConfig() - // gemlog.CheckDBConnection(config) + gemlog.CheckDBConnection(config) if err != nil { fmt.Printf("Error loading config: %v", err) os.Exit(1) From 4d1f3f2f3ee824de0d99b112428fefe87222afe1 Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Thu, 2 Oct 2025 12:54:43 +0200 Subject: [PATCH 2/3] move actions list to seprate file --- actionsList.go | 84 +++++++++++++++++++++++++++ main.go | 151 +++++++++++++++---------------------------------- 2 files changed, 129 insertions(+), 106 deletions(-) create mode 100644 actionsList.go diff --git a/actionsList.go b/actionsList.go new file mode 100644 index 0000000..c1df8e1 --- /dev/null +++ b/actionsList.go @@ -0,0 +1,84 @@ +package main + +import ( + "fmt" + "gemini_site/gemlog" + + tea "github.com/charmbracelet/bubbletea" +) + +type Action string + +const ( + Write Action = "write" + Read Action = "read" + Edit Action = "edit" + Delete Action = "delete" +) + +var actions = []Action{Write, Read, Edit, Delete} + +type ActionListPageModel struct { + cursor int +} + +func initialActionListPageModel() ActionListPageModel { + return ActionListPageModel{ + cursor: 0, + } +} + +func (m ActionListPageModel) InitActionListPage() tea.Cmd { + return nil +} + +func (m ActionListPageModel) Update(msg tea.Msg, ctx *context) (ActionListPageModel, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "up", "k": + if m.cursor > 0 { + m.cursor-- + } + case "down", "j": + if m.cursor < len(actions)-1 { + m.cursor++ + } + case "enter", " ": + action := actions[m.cursor] + switch action { + case Write: + return m, gemlog.WritePostCMD(ctx.config) + // case Read: + // m.ui.page = EntryList + // m.ui.entryListPage.cursor = 0 + // m.ui.entryListPage.actionToTake = Read + // return m, gemlog.LoadGemlogCMD(ctx.config) + // case Edit: + // m.ui.page = EntryList + // m.ui.entryListPage.cursor = 0 + // m.ui.entryListPage.actionToTake = Edit + // return m, gemlog.LoadGemlogCMD(ctx.config) + // case Delete: + // m.ui.page = EntryList + // m.ui.entryListPage.cursor = 0 + // m.ui.entryListPage.actionToTake = Delete + // return m, gemlog.LoadGemlogCMD(m.context.config) + } + } + } + + return m, nil +} + +func (m ActionListPageModel) View() string { + s := "Welcome to gemlog cli!\n\nWhat post action would you like to take?\n\n" + for i, action := range actions { + cursor := " " + if m.cursor == i { + cursor = ">" + } + s += fmt.Sprintf("%s %s\n", cursor, action) + } + return s +} diff --git a/main.go b/main.go index ce5cde7..d6520ec 100644 --- a/main.go +++ b/main.go @@ -9,46 +9,27 @@ import ( tea "github.com/charmbracelet/bubbletea" ) -type Action string - -const ( - Write Action = "write" - Read Action = "read" - Edit Action = "edit" - Delete Action = "delete" -) - -var actions = []Action{Write, Read, Edit, Delete} - -func TODOCmd() tea.Msg { - return gemlog.Notification("This action has not been implemented yet. Try another.") -} - type Page string const ( ActionList Page = "actionList" - EntryList Page = "entryList" - Entry Page = "entry" + // EntryList Page = "entryList" + // Entry Page = "entry" ) -type entryListPageModel struct { - entries []gemlog.GemlogListEntry - actionToTake Action - cursor int -} - -type actionListPageModel struct { - cursor int -} +// type entryListPageModel struct { +// entries []gemlog.GemlogListEntry +// actionToTake Action +// cursor int +// } type uiState struct { notification string errorTxt string - page Page - entryListPage entryListPageModel - actionListPage actionListPageModel + page Page + // entryListPage entryListPageModel + actionListPage ActionListPageModel } type context struct { @@ -64,13 +45,11 @@ func initialModel(config *gemlog.Config) model { return model{ ui: uiState{ page: ActionList, - entryListPage: entryListPageModel{ - cursor: 0, - entries: []gemlog.GemlogListEntry{}, - }, - actionListPage: actionListPageModel{ - cursor: 0, - }, + // entryListPage: entryListPageModel{ + // cursor: 0, + // entries: []gemlog.GemlogListEntry{}, + // }, + actionListPage: initialActionListPageModel(), }, context: &context{ config: config, @@ -79,70 +58,35 @@ func initialModel(config *gemlog.Config) model { } func (m model) Init() tea.Cmd { - return tea.SetWindowTitle("Gemlog CLI") + cmds := make([]tea.Cmd, 0) + cmds = append(cmds, tea.SetWindowTitle("Gemlog CLI")) + // TODO: add init commands from other pages + return tea.Batch(cmds...) } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + cmds := make([]tea.Cmd, 0) switch msg := msg.(type) { case gemlog.ErrorMsg: m.ui.errorTxt = fmt.Sprintf("%s\n\n", fmt.Errorf("%s", msg)) case gemlog.Notification: m.ui.notification = fmt.Sprintf("%s\n\n", string(msg)) - case gemlog.GemLogsLoaded: - m.ui.entryListPage.entries = msg.Logs - m.ui.entryListPage.cursor = 0 case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit - case "up", "k": - var cursor *int - switch m.ui.page { - case EntryList: - cursor = &m.ui.entryListPage.cursor - case ActionList: - cursor = &m.ui.actionListPage.cursor - } - if cursor != nil && *cursor > 0 { - (*cursor)-- - } - case "down", "j": - var cursor *int - if m.ui.page == EntryList && m.ui.entryListPage.cursor < len(m.ui.entryListPage.entries)-1 { - cursor = &m.ui.entryListPage.cursor - } else if m.ui.page == ActionList && m.ui.actionListPage.cursor < len(actions)-1 { - cursor = &m.ui.actionListPage.cursor - } - if cursor != nil { - (*cursor)++ - } - case "enter", " ": - if m.ui.page == ActionList { - action := actions[m.ui.actionListPage.cursor] - switch action { - case Write: - return m, gemlog.WritePostCMD(m.context.config) - case Read: - m.ui.page = EntryList - m.ui.entryListPage.cursor = 0 - m.ui.entryListPage.actionToTake = Read - return m, gemlog.LoadGemlogCMD(m.context.config) - case Edit: - m.ui.page = EntryList - m.ui.entryListPage.cursor = 0 - m.ui.entryListPage.actionToTake = Edit - return m, gemlog.LoadGemlogCMD(m.context.config) - case Delete: - m.ui.page = EntryList - m.ui.entryListPage.cursor = 0 - m.ui.entryListPage.actionToTake = Delete - return m, gemlog.LoadGemlogCMD(m.context.config) - } - } } } - return m, nil + // Delegate to the active page + switch m.ui.page { + case ActionList: + m2, cmd := m.ui.actionListPage.Update(msg, m.context) + m.ui.actionListPage = m2 + cmds = append(cmds, cmd) + } + + return m, tea.Batch(cmds...) } func (m model) View() string { @@ -155,26 +99,19 @@ func (m model) View() string { } if m.ui.page == ActionList { - s += "Welcome to gemlog cli!\n\nWhat post action would you like to take?\n\n" - for i, action := range actions { - cursor := " " - if m.ui.actionListPage.cursor == i { - cursor = ">" - } - s += fmt.Sprintf("%s %s\n", cursor, action) - } + s += m.ui.actionListPage.View() } - if m.ui.page == EntryList { - s += fmt.Sprintf("Which entry would you like to %s\n\n", m.ui.entryListPage.actionToTake) - for i, entry := range m.ui.entryListPage.entries { - cursor := " " - if m.ui.entryListPage.cursor == i { - cursor = ">" - } - s += fmt.Sprintf("%s %s : %s\n", cursor, entry.Date, entry.Slug) - } - } + // if m.ui.page == EntryList { + // s += fmt.Sprintf("Which entry would you like to %s\n\n", m.ui.entryListPage.actionToTake) + // for i, entry := range m.ui.entryListPage.entries { + // cursor := " " + // if m.ui.entryListPage.cursor == i { + // cursor = ">" + // } + // s += fmt.Sprintf("%s %s : %s\n", cursor, entry.Date, entry.Slug) + // } + // } s += "\nPress q to quit.\n" @@ -190,13 +127,15 @@ func main() { defer f.Close() slog.Info("Starting gemlog cli") config, err := gemlog.LoadConfig() - gemlog.CheckDBConnection(config) if err != nil { fmt.Printf("Error loading config: %v", err) os.Exit(1) } - - // TODO: check if we can reach db before starting program + err = gemlog.CheckDBConnection(config) + if err != nil { + fmt.Printf("Error checking db connection: %v", err) + os.Exit(1) + } p := tea.NewProgram(initialModel(config)) if _, err := p.Run(); err != nil { fmt.Printf("Alas, there's been an error: %v", err) From b680a2f5d77dd2373eb963df086de6ad62843ad2 Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Thu, 2 Oct 2025 18:19:11 +0200 Subject: [PATCH 3/3] get seprate file for entry list working --- actionsList.go | 11 +++---- entryList.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ gemlog/list.go | 2 +- main.go | 41 ++++++++++---------------- 4 files changed, 102 insertions(+), 32 deletions(-) create mode 100644 entryList.go diff --git a/actionsList.go b/actionsList.go index c1df8e1..430021c 100644 --- a/actionsList.go +++ b/actionsList.go @@ -49,11 +49,12 @@ func (m ActionListPageModel) Update(msg tea.Msg, ctx *context) (ActionListPageMo switch action { case Write: return m, gemlog.WritePostCMD(ctx.config) - // case Read: - // m.ui.page = EntryList - // m.ui.entryListPage.cursor = 0 - // m.ui.entryListPage.actionToTake = Read - // return m, gemlog.LoadGemlogCMD(ctx.config) + case Read: + switchPageCmd := func() tea.Msg { + return SwitchPages{Page: EntryList} + } + loadGemLogsCmd := gemlog.LoadGemlogCMD(ctx.config) + return m, tea.Batch(switchPageCmd, loadGemLogsCmd) // case Edit: // m.ui.page = EntryList // m.ui.entryListPage.cursor = 0 diff --git a/entryList.go b/entryList.go new file mode 100644 index 0000000..af9e8c3 --- /dev/null +++ b/entryList.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "gemini_site/gemlog" + + tea "github.com/charmbracelet/bubbletea" +) + +type EntryListPageModel struct { + entries []gemlog.GemlogListEntry + actionToTake Action + cursor int +} + +func InitialEntryListPageModel() EntryListPageModel { + return EntryListPageModel{ + cursor: 0, + actionToTake: Read, + } +} + +func (m EntryListPageModel) InitEntryListPage() tea.Cmd { + return nil +} + +func (m EntryListPageModel) Update(msg tea.Msg, ctx *context) (EntryListPageModel, tea.Cmd) { + switch msg := msg.(type) { + case gemlog.GemLogsLoaded: + m.entries = msg.Logs + return m, nil + case tea.KeyMsg: + switch msg.String() { + case "up", "k": + if m.cursor > 0 { + m.cursor-- + } + case "down", "j": + if m.cursor < len(actions)-1 { + m.cursor++ + } + // case "enter", " ": + + // action := actions[m.cursor] + // switch action { + // case Write: + // return m, gemlog.WritePostCMD(ctx.config) + // case Read: + // m.ui.page = EntryList + // m.ui.entryListPage.cursor = 0 + // m.ui.entryListPage.actionToTake = Read + // return m, gemlog.LoadGemlogCMD(ctx.config) + // case Edit: + // m.ui.page = EntryList + // m.ui.entryListPage.cursor = 0 + // m.ui.entryListPage.actionToTake = Edit + // return m, gemlog.LoadGemlogCMD(ctx.config) + // case Delete: + // m.ui.page = EntryList + // m.ui.entryListPage.cursor = 0 + // m.ui.entryListPage.actionToTake = Delete + // return m, gemlog.LoadGemlogCMD(m.context.config) + // } + } + } + + return m, nil +} + +func (m EntryListPageModel) View() string { + s := fmt.Sprintf("Which entry would you like to %s\n\n", m.actionToTake) + for i, entry := range m.entries { + cursor := " " + if m.cursor == i { + cursor = ">" + } + s += fmt.Sprintf("%s %s : %s\n", cursor, entry.Date, entry.Slug) + } + return s +} diff --git a/gemlog/list.go b/gemlog/list.go index 90c7226..ce51dfc 100644 --- a/gemlog/list.go +++ b/gemlog/list.go @@ -10,6 +10,6 @@ func LoadGemlogCMD(config *Config) tea.Cmd { if err != nil { return ErrorMsg{err} } - return GemLogsLoaded{logs} + return GemLogsLoaded{Logs: logs} } } diff --git a/main.go b/main.go index d6520ec..6b2d5e8 100644 --- a/main.go +++ b/main.go @@ -13,22 +13,18 @@ type Page string const ( ActionList Page = "actionList" - // EntryList Page = "entryList" + EntryList Page = "entryList" // Entry Page = "entry" ) -// type entryListPageModel struct { -// entries []gemlog.GemlogListEntry -// actionToTake Action -// cursor int -// } +type SwitchPages struct{ Page Page } type uiState struct { notification string errorTxt string - page Page - // entryListPage entryListPageModel + page Page + entryListPage EntryListPageModel actionListPage ActionListPageModel } @@ -67,6 +63,8 @@ func (m model) Init() tea.Cmd { func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds := make([]tea.Cmd, 0) switch msg := msg.(type) { + case SwitchPages: + m.ui.page = msg.Page case gemlog.ErrorMsg: m.ui.errorTxt = fmt.Sprintf("%s\n\n", fmt.Errorf("%s", msg)) case gemlog.Notification: @@ -78,13 +76,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } - // Delegate to the active page - switch m.ui.page { - case ActionList: - m2, cmd := m.ui.actionListPage.Update(msg, m.context) - m.ui.actionListPage = m2 - cmds = append(cmds, cmd) - } + actionListM, cmd := m.ui.actionListPage.Update(msg, m.context) + m.ui.actionListPage = actionListM + cmds = append(cmds, cmd) + + entryListM, cmd := m.ui.entryListPage.Update(msg, m.context) + m.ui.entryListPage = entryListM + cmds = append(cmds, cmd) return m, tea.Batch(cmds...) } @@ -100,19 +98,10 @@ func (m model) View() string { if m.ui.page == ActionList { s += m.ui.actionListPage.View() + } else if m.ui.page == EntryList { + s += m.ui.entryListPage.View() } - // if m.ui.page == EntryList { - // s += fmt.Sprintf("Which entry would you like to %s\n\n", m.ui.entryListPage.actionToTake) - // for i, entry := range m.ui.entryListPage.entries { - // cursor := " " - // if m.ui.entryListPage.cursor == i { - // cursor = ">" - // } - // s += fmt.Sprintf("%s %s : %s\n", cursor, entry.Date, entry.Slug) - // } - // } - s += "\nPress q to quit.\n" return s