From bed51528994bd57de2d7df2e34f3e6aac031f2d3 Mon Sep 17 00:00:00 2001 From: GitBluub Date: Sun, 5 May 2024 18:51:54 +0200 Subject: [PATCH] big refacto --- tui/cmd/keymaps.go | 52 ++++++++ tui/cmd/main.go | 265 ++++++--------------------------------- tui/cmd/search.go | 90 +++++++++++++ tui/cmd/update.go | 215 +++++++++++++++++++++++++++++++ tui/cmd/views.go | 10 +- tui/models/entry.go | 48 ------- tui/models/feed.go | 27 ++++ tui/pages/feeds/model.go | 72 +++++++++++ 8 files changed, 496 insertions(+), 283 deletions(-) create mode 100644 tui/cmd/keymaps.go create mode 100644 tui/cmd/search.go create mode 100644 tui/cmd/update.go create mode 100644 tui/models/feed.go diff --git a/tui/cmd/keymaps.go b/tui/cmd/keymaps.go new file mode 100644 index 0000000..0f9f410 --- /dev/null +++ b/tui/cmd/keymaps.go @@ -0,0 +1,52 @@ +package main + +import "github.com/charmbracelet/bubbles/key" + +type ListKeyMap struct { + Query key.Binding + BookmarkToggle key.Binding + ReadToggle key.Binding + ReadLaterToggle key.Binding + IgnoreToggle key.Binding + PreviewPost key.Binding + GoToFeeds key.Binding + GoToPosts key.Binding +} + +func NewListKeyMap() *ListKeyMap { + return &ListKeyMap{ + GoToFeeds: key.NewBinding( + key.WithKeys("f"), + key.WithHelp("f", "go to feeds"), + ), + GoToPosts: key.NewBinding( + key.WithKeys("p"), + key.WithHelp("p", "go to posts"), + ), + PreviewPost: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "preview post"), + ), + Query: key.NewBinding( + key.WithKeys("/"), + key.WithHelp("/", "query posts"), + ), + BookmarkToggle: key.NewBinding( + key.WithKeys("b"), + key.WithHelp("b", "toggle bookmarked"), + ), + ReadToggle: key.NewBinding( + key.WithKeys("r"), + key.WithHelp("r", "toggle mark as read"), + ), + + IgnoreToggle: key.NewBinding( + key.WithKeys("x", "d"), + key.WithHelp("d/x", "ignore post"), + ), + ReadLaterToggle: key.NewBinding( + key.WithKeys("m"), + key.WithHelp("m", "add to read later"), + ), + } +} diff --git a/tui/cmd/main.go b/tui/cmd/main.go index fda3b51..5853b0c 100644 --- a/tui/cmd/main.go +++ b/tui/cmd/main.go @@ -2,7 +2,6 @@ package main import ( "os" - "strings" "time" "github.com/charmbracelet/bubbles/key" @@ -10,39 +9,53 @@ import ( "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" - huh "github.com/charmbracelet/huh" "github.com/google/uuid" . "github.com/zoryia/vex/tui/models" . "github.com/zoryia/vex/tui/pages" "github.com/zoryia/vex/tui/pages/auth" + "github.com/zoryia/vex/tui/pages/feeds" "github.com/zoryia/vex/tui/pages/preview" ) type Model struct { - list list.Model - textInput textinput.Model - err error - auth auth.Model - page VexPage - query string - feeds []Feed - entries []Entry - tags []string - keys *ListKeyMap - Preview preview.Model + // entries + list list.Model + queryInput textinput.Model + + err error + page VexPage + tags []string + keys *ListKeyMap + + Preview preview.Model + Feeds feeds.Model + Auth auth.Model } -func New() *Model { +func queryInput() textinput.Model { ti := textinput.New() ti.Placeholder = "Search Query" ti.CharLimit = 156 ti.Width = 56 - return &Model{textInput: ti, auth: auth.New(), page: ENTRIES, keys: NewListKeyMap(), Preview: preview.Model{Viewport: viewport.New(0, 0)}} + return ti + +} + +func New() *Model { + return &Model{ + queryInput: queryInput(), + page: LOGIN, + keys: NewListKeyMap(), + + Preview: preview.Model{Viewport: viewport.New(0, 0)}, + Feeds: feeds.New(), + Auth: auth.New(), + } } func (m Model) getEverything() tea.Cmd { return func() tea.Msg { - return tea.Batch(getEntries(m.auth.Jwt)) // getTags, getFeeds) + return tea.Batch(getEntries(m.Auth.Jwt)) // getTags, getFeeds) } } @@ -50,6 +63,7 @@ func (m *Model) initList(width int, height int) { m.list = list.New([]list.Item{}, list.NewDefaultDelegate(), width, height) m.list.Title = "Posts" m.list.SetFilteringEnabled(false) + m.list.DisableQuitKeybindings() m.list.AdditionalShortHelpKeys = func() []key.Binding { return []key.Binding{ m.keys.Query, @@ -58,6 +72,7 @@ func (m *Model) initList(width int, height int) { } m.list.AdditionalFullHelpKeys = func() []key.Binding { return []key.Binding{ + m.keys.GoToFeeds, m.keys.ReadToggle, m.keys.ReadLaterToggle, m.keys.BookmarkToggle, @@ -75,219 +90,11 @@ func (m *Model) initList(width int, height int) { } func (m Model) Init() tea.Cmd { - return tea.Batch(m.auth.LoginForm.Init(), m.auth.RegisterForm.Init(), checkJwt(m.auth.Jwt)) - -} - -func (m Model) handleSearchCompletion() (Model, tea.Cmd) { - var cmd tea.Cmd - queryWords := strings.Split(m.textInput.Value(), " ") - if len(queryWords) == 0 { - return m, cmd - } - lastWord := queryWords[len(queryWords)-1] - if lastWord == "tag:" { - var feed string - huh.NewSelect[string](). - Title("Pick a feed."). - Options( - huh.NewOption("United States", "US"), - huh.NewOption("Germany", "DE"), - huh.NewOption("Brazil", "BR"), - huh.NewOption("Canada", "CA"), - ). - Value(&feed).Run() - m.textInput.SetValue(m.textInput.Value() + feed) - m.textInput.CursorEnd() - } - - if lastWord == "feed:" { - var feed string - var feeds = []string{"Devops", "System", "Angular"} - huh.NewSelect[string](). - Title("Pick a feed."). - Options( - huh.NewOptions(feeds...)..., - ). - Value(&feed).Run() - m.textInput.SetValue(m.textInput.Value() + feed) - m.textInput.CursorEnd() - } - return m, cmd -} - -func (m *Model) deleteWordBackward() { - if m.textInput.Position() == 0 || len(m.textInput.Value()) == 0 { - return - } - - // TODO: wtf are other echo modes, dont care - //if m.textInput.EchoMode != textinput.EchoNormal { - // m.deleteBeforeCursor() - // return - //} - - // Linter note: it's critical that we acquire the initial cursor position - // here prior to altering it via SetCursor() below. As such, moving this - // call into the corresponding if clause does not apply here. - oldPos := m.textInput.Position() //nolint:ifshort - - m.textInput.SetCursor(oldPos - 1) - // ECHO character? - for m.textInput.Value()[m.textInput.Position()] == ' ' { - if m.textInput.Position() <= 0 { - break - } - // ignore series of whitespace before cursor - m.textInput.SetCursor(m.textInput.Position() - 1) - } - - for m.textInput.Position() > 0 { - if m.textInput.Value()[m.textInput.Position()] != ' ' { - m.textInput.SetCursor(m.textInput.Position() - 1) - } else { - if m.textInput.Position() > 0 { - // keep the previous space - m.textInput.SetCursor(m.textInput.Position() + 1) - } - break - } - } - - if oldPos > len(m.textInput.Value()) { - m.textInput.SetValue(m.textInput.Value()[:m.textInput.Position()]) - } else { - m.textInput.SetValue(m.textInput.Value()[:m.textInput.Position()] + m.textInput.Value()[oldPos:]) - } -} - -func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - - var cmds []tea.Cmd - var blurredNow = false - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.initList(msg.Width, msg.Height-2) - m.textInput.Width = msg.Width - 5 - m.Preview.Viewport.Width = msg.Width - m.Preview.Viewport.Height = msg.Height - m.Preview.VerticalMarginHeight() - - case invalidJwtMsg: - m.auth.Jwt = new(string) - m.page = LOGIN - return m, nil - case loginSuccessMsg: - *m.auth.Jwt = msg.string - m.page = FEEDS - return m, m.getEverything() - - case registerSuccessMsg: - *m.auth.Jwt = msg.string - m.page = FEEDS - return m, m.getEverything() - - case tea.KeyMsg: - switch msg.Type { - case tea.KeyCtrlC, tea.KeyEsc: - return m, tea.Quit - case tea.KeyCtrlT: - if m.page == LOGIN { - m.page = REGISTER - } else if m.page == REGISTER { - m.page = LOGIN - } - case tea.KeyEnter: - if m.textInput.Focused() { - // Get entries with query - m.textInput.Blur() - blurredNow = true - } - } - switch { - case key.Matches(msg, m.textInput.KeyMap.DeleteCharacterBackward): //TODO: add only when query - words := strings.Split(m.textInput.Value(), " ") - if len(words) > 0 && (strings.HasPrefix(words[len(words)-1], "tag:") || strings.HasPrefix(words[len(words)-1], "feed:")) { - m.deleteWordBackward() - } - case key.Matches(msg, m.keys.IgnoreToggle) && m.page == "FEEDS": - var e = m.list.SelectedItem().(Entry) - cmds = append(cmds, ignorePost(m.auth.Jwt, e)) - - case key.Matches(msg, m.keys.ReadToggle): - var e = m.list.SelectedItem().(Entry) - cmds = append(cmds, toggleRead(m.auth.Jwt, e)) - - case key.Matches(msg, m.keys.ReadLaterToggle): - var e = m.list.SelectedItem().(Entry) - cmds = append(cmds, toggleReadLater(m.auth.Jwt, e)) - - case key.Matches(msg, m.keys.BookmarkToggle): - var e = m.list.SelectedItem().(Entry) - cmds = append(cmds, toggleBookmark(m.auth.Jwt, e)) - - case key.Matches(msg, m.keys.Query): - m.textInput.Focus() - m.textInput.SetValue("") - - case key.Matches(msg, m.keys.PreviewPost) && m.textInput.Focused() == false && blurredNow == false: - var e = m.list.SelectedItem().(Entry) - m.Preview.Entry = e - m.Preview.Viewport.SetContent(e.Content) - m.page = PREVIEW - } - - } - - // Process the form - // LOGIN - if m.page == LOGIN { - form, cmd := m.auth.LoginForm.Update(msg) - if f, ok := form.(*huh.Form); ok { - m.auth.LoginForm = f - cmds = append(cmds, cmd) - } - - if m.auth.LoginForm.State == huh.StateCompleted { - username := m.auth.LoginForm.GetString("email") - password := m.auth.LoginForm.GetString("password") - cmds = append(cmds, login(username, password)) - } - } - - if m.page == REGISTER { - - // Process the form - // LOGIN - registerForm, cmd := m.auth.RegisterForm.Update(msg) - if f, ok := registerForm.(*huh.Form); ok { - m.auth.RegisterForm = f - cmds = append(cmds, cmd) - } - - if m.auth.RegisterForm.State == huh.StateCompleted { - username := m.auth.RegisterForm.GetString("username") - password := m.auth.RegisterForm.GetString("password") - email := m.auth.RegisterForm.GetString("email") - cmds = append(cmds, register(username, password, email)) - } - - } - - var cmd tea.Cmd - m.Preview.Viewport, cmd = m.Preview.Viewport.Update(msg) - cmds = append(cmds, cmd) - m.list, cmd = m.list.Update(msg) - cmds = append(cmds, cmd) - m.textInput, cmd = m.textInput.Update(msg) - cmds = append(cmds, cmd) - switch msg := msg.(type) { - case tea.KeyMsg: - _ = msg - m, cmd = m.handleSearchCompletion() - cmds = append(cmds, cmd) - } - - return m, tea.Batch(cmds...) + return tea.Batch( + m.Auth.LoginForm.Init(), + m.Auth.RegisterForm.Init(), + checkJwt(m.Auth.Jwt), + ) } func main() { diff --git a/tui/cmd/search.go b/tui/cmd/search.go new file mode 100644 index 0000000..c7aa446 --- /dev/null +++ b/tui/cmd/search.go @@ -0,0 +1,90 @@ +package main + +import ( + "strings" + + tea "github.com/charmbracelet/bubbletea" + huh "github.com/charmbracelet/huh" +) + +func (m Model) handleSearchCompletion() (Model, tea.Cmd) { + var cmd tea.Cmd + queryWords := strings.Split(m.queryInput.Value(), " ") + if len(queryWords) == 0 { + return m, cmd + } + lastWord := queryWords[len(queryWords)-1] + if lastWord == "tag:" { + var feed string + huh.NewSelect[string](). + Title("Pick a feed."). + Options( + huh.NewOption("United States", "US"), + huh.NewOption("Germany", "DE"), + huh.NewOption("Brazil", "BR"), + huh.NewOption("Canada", "CA"), + ). + Value(&feed).Run() + m.queryInput.SetValue(m.queryInput.Value() + feed) + m.queryInput.CursorEnd() + } + + if lastWord == "feed:" { + var feed string + var feeds = []string{"Devops", "System", "Angular"} + huh.NewSelect[string](). + Title("Pick a feed."). + Options( + huh.NewOptions(feeds...)..., + ). + Value(&feed).Run() + m.queryInput.SetValue(m.queryInput.Value() + feed) + m.queryInput.CursorEnd() + } + return m, cmd +} + +func (m *Model) deleteWordBackward() { + if m.queryInput.Position() == 0 || len(m.queryInput.Value()) == 0 { + return + } + + // TODO: wtf are other echo modes, dont care + //if m.textInput.EchoMode != textinput.EchoNormal { + // m.deleteBeforeCursor() + // return + //} + + // Linter note: it's critical that we acquire the initial cursor position + // here prior to altering it via SetCursor() below. As such, moving this + // call into the corresponding if clause does not apply here. + oldPos := m.queryInput.Position() //nolint:ifshort + + m.queryInput.SetCursor(oldPos - 1) + // ECHO character? + for m.queryInput.Value()[m.queryInput.Position()] == ' ' { + if m.queryInput.Position() <= 0 { + break + } + // ignore series of whitespace before cursor + m.queryInput.SetCursor(m.queryInput.Position() - 1) + } + + for m.queryInput.Position() > 0 { + if m.queryInput.Value()[m.queryInput.Position()] != ' ' { + m.queryInput.SetCursor(m.queryInput.Position() - 1) + } else { + if m.queryInput.Position() > 0 { + // keep the previous space + m.queryInput.SetCursor(m.queryInput.Position() + 1) + } + break + } + } + + if oldPos > len(m.queryInput.Value()) { + m.queryInput.SetValue(m.queryInput.Value()[:m.queryInput.Position()]) + } else { + m.queryInput.SetValue(m.queryInput.Value()[:m.queryInput.Position()] + m.queryInput.Value()[oldPos:]) + } +} diff --git a/tui/cmd/update.go b/tui/cmd/update.go new file mode 100644 index 0000000..c0da9fe --- /dev/null +++ b/tui/cmd/update.go @@ -0,0 +1,215 @@ +package main + +import ( + "strings" + + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + huh "github.com/charmbracelet/huh" + + . "github.com/zoryia/vex/tui/models" + . "github.com/zoryia/vex/tui/pages" +) + +func (m Model) LoginUpdate(msg tea.Msg) (tea.Model, []tea.Cmd) { + var cmds []tea.Cmd + if m.page != LOGIN { + return m, nil + } + return m, cmds +} + +func (m Model) GlobalUpdate(msg tea.Msg) (Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.initList(msg.Width, msg.Height-2) + m.queryInput.Width = msg.Width - 5 + m.Preview.Viewport.Width = msg.Width + m.Preview.Viewport.Height = msg.Height - m.Preview.VerticalMarginHeight() + m.Feeds.List.SetWidth(msg.Width) + m.Feeds.List.SetHeight(msg.Height) + + case invalidJwtMsg: + m.Auth.Jwt = new(string) + m.page = LOGIN + return m, nil + + case loginSuccessMsg: + *m.Auth.Jwt = msg.string + m.page = ENTRIES + return m, m.getEverything() + + case registerSuccessMsg: + *m.Auth.Jwt = msg.string + m.page = ENTRIES + return m, m.getEverything() + + case tea.KeyMsg: + switch msg.Type { + case tea.KeyCtrlC, tea.KeyEsc: + return m, tea.Quit + } + } + return m, nil +} + +func loginUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) { + var cmds []tea.Cmd + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case tea.KeyCtrlT: + m.page = REGISTER + } + } + form, cmd := m.Auth.LoginForm.Update(msg) + if f, ok := form.(*huh.Form); ok { + m.Auth.LoginForm = f + cmds = append(cmds, cmd) + } + + if m.Auth.LoginForm.State == huh.StateCompleted { + username := m.Auth.LoginForm.GetString("email") + password := m.Auth.LoginForm.GetString("password") + cmds = append(cmds, login(username, password)) + } + + return m, tea.Batch(cmds...) +} +func registerUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) { + var cmds []tea.Cmd + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case tea.KeyCtrlT: + m.page = LOGIN + } + } + + // Process the form + // LOGIN + registerForm, cmd := m.Auth.RegisterForm.Update(msg) + if f, ok := registerForm.(*huh.Form); ok { + m.Auth.RegisterForm = f + cmds = append(cmds, cmd) + } + + if m.Auth.RegisterForm.State == huh.StateCompleted { + username := m.Auth.RegisterForm.GetString("username") + password := m.Auth.RegisterForm.GetString("password") + email := m.Auth.RegisterForm.GetString("email") + cmds = append(cmds, register(username, password, email)) + } + + return m, tea.Batch(cmds...) +} +func entriesUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) { + var cmds []tea.Cmd + var cmd tea.Cmd + + var blurredNow = false + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case tea.KeyEnter: + if m.queryInput.Focused() { + // Get entries with query + m.queryInput.Blur() + blurredNow = true + } + } + switch { + case key.Matches(msg, m.queryInput.KeyMap.DeleteCharacterBackward) && m.queryInput.Focused(): + words := strings.Split(m.queryInput.Value(), " ") + if len(words) > 0 && (strings.HasPrefix(words[len(words)-1], "tag:") || strings.HasPrefix(words[len(words)-1], "feed:")) { + m.deleteWordBackward() + } + } + if m.queryInput.Focused() == false { + switch { + case key.Matches(msg, m.keys.GoToFeeds): + m.page = FEEDS + + case key.Matches(msg, m.keys.IgnoreToggle): + var e = m.list.SelectedItem().(Entry) + cmds = append(cmds, ignorePost(m.Auth.Jwt, e)) + + case key.Matches(msg, m.keys.ReadToggle): + var e = m.list.SelectedItem().(Entry) + cmds = append(cmds, toggleRead(m.Auth.Jwt, e)) + + case key.Matches(msg, m.keys.ReadLaterToggle): + var e = m.list.SelectedItem().(Entry) + cmds = append(cmds, toggleReadLater(m.Auth.Jwt, e)) + + case key.Matches(msg, m.keys.BookmarkToggle): + var e = m.list.SelectedItem().(Entry) + cmds = append(cmds, toggleBookmark(m.Auth.Jwt, e)) + + case key.Matches(msg, m.keys.Query): + m.queryInput.Focus() + m.queryInput.SetValue("") + + case key.Matches(msg, m.keys.PreviewPost) && m.queryInput.Focused() == false && blurredNow == false: + var e = m.list.SelectedItem().(Entry) + m.Preview.Entry = e + m.Preview.Viewport.SetContent(e.Content) + m.page = PREVIEW + + } + } + m, cmd = m.handleSearchCompletion() + cmds = append(cmds, cmd) + } + m.list, cmd = m.list.Update(msg) + cmds = append(cmds, cmd) + m.queryInput, cmd = m.queryInput.Update(msg) + cmds = append(cmds, cmd) + return m, tea.Batch(cmds...) +} +func feedsUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) { + return m, nil +} +func tagsUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) { + return m, nil +} +func previewUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) { + var cmds []tea.Cmd + var cmd tea.Cmd + m.Preview.Viewport, cmd = m.Preview.Viewport.Update(msg) + cmds = append(cmds, cmd) + + return m, tea.Batch(cmds...) +} +func ignoredUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) { + return m, nil +} + +type updateFunc func(Model, tea.Msg) (Model, tea.Cmd) + +func getUpdateMap() map[VexPage]updateFunc { + + updateMap := make(map[VexPage]updateFunc) + updateMap[LOGIN] = loginUpdate + updateMap[REGISTER] = registerUpdate + updateMap[ENTRIES] = entriesUpdate + updateMap[FEEDS] = feedsUpdate + updateMap[TAGS] = tagsUpdate + updateMap[IGNORED] = ignoredUpdate + updateMap[PREVIEW] = previewUpdate + return updateMap +} + +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + updateMap := getUpdateMap() + var cmds []tea.Cmd + var cmd tea.Cmd + + m, cmd = m.GlobalUpdate(msg) + cmds = append(cmds, cmd) + m, cmd = updateMap[m.page](m, msg) + cmds = append(cmds, cmd) + return m, tea.Batch(cmds...) +} diff --git a/tui/cmd/views.go b/tui/cmd/views.go index 94d0889..0f113ca 100644 --- a/tui/cmd/views.go +++ b/tui/cmd/views.go @@ -1,8 +1,6 @@ package main import ( - "fmt" - "github.com/charmbracelet/lipgloss" . "github.com/zoryia/vex/tui/pages" ) @@ -10,15 +8,15 @@ import ( func (m Model) AuthView() string { return lipgloss.JoinHorizontal( lipgloss.Left, - m.auth.LoginForm.View(), - m.auth.RegisterForm.View(), + m.Auth.LoginForm.View(), + m.Auth.RegisterForm.View(), ) } func (m Model) EntriesView() string { - return lipgloss.JoinVertical(lipgloss.Left, m.textInput.View(), m.list.View()) + return lipgloss.JoinVertical(lipgloss.Left, m.queryInput.View(), m.list.View()) } func (m Model) FeedsView() string { - return fmt.Sprintf("%s ", *m.auth.Jwt) + return m.Feeds.View() } func (m Model) TagsView() string { return "" diff --git a/tui/models/entry.go b/tui/models/entry.go index d9a6d1f..625bd77 100644 --- a/tui/models/entry.go +++ b/tui/models/entry.go @@ -4,18 +4,9 @@ import ( "fmt" "time" - "github.com/charmbracelet/bubbles/key" "github.com/google/uuid" ) -type Feed struct { - Id uuid.UUID `json:"id"` - Name string `json:"name"` - Url string `json:"url"` - FaviconUrl string `json:"faviconUrl"` - Tags []string `json:"tags"` -} - type Entry struct { Id uuid.UUID `json:"id"` ArticleTitle string `json:"title"` @@ -42,42 +33,3 @@ func (e Entry) Title() string { func (e Entry) Description() string { return fmt.Sprintf("%s", "my desc") // TODO: real description (tags and author + date ?) } - -type ListKeyMap struct { - Query key.Binding - BookmarkToggle key.Binding - ReadToggle key.Binding - ReadLaterToggle key.Binding - IgnoreToggle key.Binding - PreviewPost key.Binding -} - -func NewListKeyMap() *ListKeyMap { - return &ListKeyMap{ - PreviewPost: key.NewBinding( - key.WithKeys("enter"), - key.WithHelp("enter", "preview post"), - ), - Query: key.NewBinding( - key.WithKeys("/"), - key.WithHelp("/", "query posts"), - ), - BookmarkToggle: key.NewBinding( - key.WithKeys("b"), - key.WithHelp("b", "toggle bookmarked"), - ), - ReadToggle: key.NewBinding( - key.WithKeys("r"), - key.WithHelp("r", "toggle mark as read"), - ), - - IgnoreToggle: key.NewBinding( - key.WithKeys("x", "d"), - key.WithHelp("d/x", "ignore post"), - ), - ReadLaterToggle: key.NewBinding( - key.WithKeys("m"), - key.WithHelp("m", "add to read later"), - ), - } -} diff --git a/tui/models/feed.go b/tui/models/feed.go new file mode 100644 index 0000000..baae1db --- /dev/null +++ b/tui/models/feed.go @@ -0,0 +1,27 @@ +package models + +import ( + "fmt" + + "github.com/google/uuid" +) + +type Feed struct { + Id uuid.UUID `json:"id"` + Name string `json:"name"` + Url string `json:"url"` + FaviconUrl string `json:"faviconUrl"` + Tags []string `json:"tags"` +} + +func (f Feed) FilterValue() string { + return f.Name +} + +func (f Feed) Title() string { + return f.Name +} + +func (f Feed) Description() string { + return fmt.Sprintf("%s", "my desc") // TODO: real description (tags, submitter, error status, last sync) +} diff --git a/tui/pages/feeds/model.go b/tui/pages/feeds/model.go index e69de29..be4060f 100644 --- a/tui/pages/feeds/model.go +++ b/tui/pages/feeds/model.go @@ -0,0 +1,72 @@ +package feeds + +import ( + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/huh" + "github.com/google/uuid" + + "github.com/zoryia/vex/tui/models" +) + +type Model struct { + List list.Model + AddFeed *huh.Form +} + +func (m Model) View() string { + return m.List.View() +} + +func FeedsKeys() []key.Binding { + return []key.Binding{ + key.NewBinding( + key.WithKeys("a"), + key.WithHelp("a", "Add a new feed"), + ), + key.NewBinding( + key.WithKeys("p"), + key.WithHelp("p", "Go to posts"), + ), + key.NewBinding( + key.WithKeys("x", "d"), + key.WithHelp("x/d", "Ignore feed"), + ), + } +} + +func initFeedsList() list.Model { + feeds := list.New([]list.Item{}, list.NewDefaultDelegate(), 0, 0) + feeds.Title = "Feeds" + feeds.SetFilteringEnabled(false) + feeds.DisableQuitKeybindings() + feeds.AdditionalShortHelpKeys = FeedsKeys + feeds.AdditionalFullHelpKeys = FeedsKeys + feeds.SetItems([]list.Item{ + models.Feed{Id: uuid.UUID{}, Tags: []string{"Devops", "Kubernetes"}, Name: "zwindler", Url: "zwindler.blog", FaviconUrl: "zwindler.blog.favicon"}, + models.Feed{Id: uuid.UUID{}, Tags: []string{"Devops", "Kubernetes"}, Name: "zwindler", Url: "zwindler.blog", FaviconUrl: "zwindler.blog.favicon"}, + models.Feed{Id: uuid.UUID{}, Tags: []string{"Devops", "Kubernetes"}, Name: "zwindler", Url: "zwindler.blog", FaviconUrl: "zwindler.blog.favicon"}, + }) + return feeds +} + +func AddFeedForm(tags []string) *huh.Form { + return huh.NewForm( + huh.NewGroup( + huh.NewInput(). + Title("Feed Url"). + Key("url"), + huh.NewMultiSelect[string](). + Title("Tags"). + Key("tags"). + Options( + + huh.NewOptions(tags...)..., + ), + )).WithWidth(40) +} + +func New() Model { + return Model{List: initFeedsList(), AddFeed: AddFeedForm([]string{})} + +}