From 8baaf5bae01892a138f648ecc5f0d18cae567b4a Mon Sep 17 00:00:00 2001 From: GitBluub Date: Sun, 5 May 2024 15:12:06 +0200 Subject: [PATCH 1/6] restructure and email validation --- tui/.gitignore | 1 + tui/{ => cmd}/http.go | 9 +-- tui/{ => cmd}/main.go | 119 +++++++++++++++++++-------------- tui/{pages.go => cmd/views.go} | 29 +++----- tui/entry.go | 28 -------- tui/go.mod | 23 ++++++- tui/go.sum | 56 +++++++++++++++- tui/models/entry.go | 83 +++++++++++++++++++++++ tui/{ => pages/auth}/auth.go | 34 ++++++++-- tui/pages/pages.go | 13 ++++ tui/pages/preview/model.go | 88 ++++++++++++++++++++++++ 11 files changed, 370 insertions(+), 113 deletions(-) create mode 100644 tui/.gitignore rename tui/{ => cmd}/http.go (95%) rename tui/{ => cmd}/main.go (67%) rename tui/{pages.go => cmd/views.go} (54%) delete mode 100644 tui/entry.go create mode 100644 tui/models/entry.go rename tui/{ => pages/auth}/auth.go (53%) create mode 100644 tui/pages/pages.go create mode 100644 tui/pages/preview/model.go diff --git a/tui/.gitignore b/tui/.gitignore new file mode 100644 index 0000000..9239013 --- /dev/null +++ b/tui/.gitignore @@ -0,0 +1 @@ +vex.log diff --git a/tui/http.go b/tui/cmd/http.go similarity index 95% rename from tui/http.go rename to tui/cmd/http.go index 1dd4404..696ae50 100644 --- a/tui/http.go +++ b/tui/cmd/http.go @@ -9,6 +9,7 @@ import ( "net/http" tea "github.com/charmbracelet/bubbletea" + "github.com/zoryia/vex/tui/models" ) type statusMsg int @@ -133,7 +134,7 @@ func register(username string, password string, email string) tea.Cmd { } } -type getEntriesSuccessMsg []Entry +type getEntriesSuccessMsg []models.Entry func getEntries(jwt *string) tea.Cmd { return func() tea.Msg { @@ -148,7 +149,7 @@ func getEntries(jwt *string) tea.Cmd { if err != nil { return httpErrorMsg(err) } - var entries []Entry + var entries []models.Entry err = json.Unmarshal(data, &entries) if err != nil { return httpErrorMsg(err) @@ -157,7 +158,7 @@ func getEntries(jwt *string) tea.Cmd { } } -type getFeedsSuccessMsg []Feed +type getFeedsSuccessMsg []models.Feed func getFeeds(jwt *string) tea.Cmd { return func() tea.Msg { @@ -171,7 +172,7 @@ func getFeeds(jwt *string) tea.Cmd { if err != nil { return httpErrorMsg(err) } - var feeds []Feed + var feeds []models.Feed err = json.Unmarshal(data, &feeds) if err != nil { return httpErrorMsg(err) diff --git a/tui/main.go b/tui/cmd/main.go similarity index 67% rename from tui/main.go rename to tui/cmd/main.go index 7f3b856..381c380 100644 --- a/tui/main.go +++ b/tui/cmd/main.go @@ -1,8 +1,8 @@ package main import ( - "fmt" - "net/http" + "log" + _ "log" "os" "strings" "time" @@ -10,32 +10,27 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/textinput" + "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" huh "github.com/charmbracelet/huh" + . "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/preview" ) -func (e Entry) FilterValue() string { - return e.ArticleTitle -} - -func (e Entry) Title() string { - return e.ArticleTitle -} - -func (e Entry) Description() string { - return fmt.Sprintf("%s", "my desc") // TODO: real description -} - type Model struct { list list.Model textInput textinput.Model err error - auth Auth + auth auth.Model page VexPage query string feeds []Feed entries []Entry tags []string + keys *ListKeyMap + Preview preview.Model } func New() *Model { @@ -44,12 +39,12 @@ func New() *Model { ti.Focus() ti.CharLimit = 156 ti.Width = 56 - return &Model{textInput: ti, auth: Auth{loginForm: getLoginForm(), registerForm: getRegisterForm(), jwt: new(string)}, page: LOGIN} + return &Model{textInput: ti, auth: auth.New(), page: ENTRIES, keys: NewListKeyMap(), Preview: preview.Model{Viewport: viewport.New(0, 0)}} } 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) } } @@ -59,14 +54,14 @@ func (m *Model) initList(width int, height int) { m.list.SetFilteringEnabled(false) var f = Feed{Id: "1", Tags: []string{"Devops", "Kubernetes"}, Name: "zwindler", Url: "zwindler.blog", FaviconUrl: "zwindler.blog.favicon"} m.list.SetItems([]list.Item{ - Entry{Id: "1", ArticleTitle: "yay", Content: "ouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, + Entry{Id: "1", ArticleTitle: "yay", Content: "ouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, Entry{Id: "2", ArticleTitle: "grrrrr", Content: "ouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, Entry{Id: "3", ArticleTitle: "my life is pain", Content: "ouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, }) } func (m Model) Init() tea.Cmd { - return tea.Batch(checkServer, m.auth.loginForm.Init(), m.auth.registerForm.Init(), checkJwt(m.auth.jwt)) + return tea.Batch(m.auth.LoginForm.Init(), m.auth.RegisterForm.Init(), checkJwt(m.auth.Jwt)) } @@ -152,38 +147,26 @@ func (m *Model) deleteWordBackward() { } } -const url = "localhost:3000" - -func checkServer() tea.Msg { - c := &http.Client{ - Timeout: 10 * time.Second, - } - res, err := c.Get(url) - if err != nil { - return errMsg{err} - } - defer res.Body.Close() // nolint:errcheck - - return statusMsg(res.StatusCode) - -} - func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + + var cmds []tea.Cmd switch msg := msg.(type) { case tea.WindowSizeMsg: m.initList(msg.Width, msg.Height) + m.Preview.Viewport.Width = msg.Width + m.Preview.Viewport.Height = msg.Height - m.Preview.VerticalMarginHeight() case invalidJwtMsg: - m.auth.jwt = new(string) + m.auth.Jwt = new(string) m.page = LOGIN return m, nil case loginSuccessMsg: - *m.auth.jwt = msg.string + *m.auth.Jwt = msg.string m.page = FEEDS return m, m.getEverything() case registerSuccessMsg: - *m.auth.jwt = msg.string + *m.auth.Jwt = msg.string m.page = FEEDS return m, m.getEverything() @@ -205,22 +188,50 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 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": + // TODO: ignore the post + return m, nil + + case key.Matches(msg, m.keys.ReadToggle): + // TODO: mark as read + return m, nil + + case key.Matches(msg, m.keys.ReadLaterToggle): + // TODO: add to read later + return m, nil + + case key.Matches(msg, m.keys.BookmarkToggle): + // TODO: toggle bookmark + return m, nil + + case key.Matches(msg, m.keys.Query): + // TODO: launch query input + return m, nil + + case key.Matches(msg, m.keys.PreviewPost): + var e = m.list.SelectedItem() + + entry := e.(Entry) + m.Preview.Entry = entry + m.Preview.Viewport.SetContent(entry.Content) + m.page = PREVIEW + log.Print(entry.Content) + log.Print(m.Preview.Viewport.VisibleLineCount()) } } - var cmds []tea.Cmd // Process the form // LOGIN if m.page == LOGIN { - form, cmd := m.auth.loginForm.Update(msg) + form, cmd := m.auth.LoginForm.Update(msg) if f, ok := form.(*huh.Form); ok { - m.auth.loginForm = f + 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") + 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)) } } @@ -229,21 +240,24 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Process the form // LOGIN - registerForm, cmd := m.auth.registerForm.Update(msg) + registerForm, cmd := m.auth.RegisterForm.Update(msg) if f, ok := registerForm.(*huh.Form); ok { - m.auth.registerForm = f + 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") + 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) @@ -261,7 +275,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func main() { tea.LogToFile("vex.log", "") m := New() - p := tea.NewProgram(m) + p := tea.NewProgram(m, + tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer" + tea.WithMouseCellMotion(), + ) if _, err := p.Run(); err != nil { os.Exit(1) } diff --git a/tui/pages.go b/tui/cmd/views.go similarity index 54% rename from tui/pages.go rename to tui/cmd/views.go index ea11a64..319ed8f 100644 --- a/tui/pages.go +++ b/tui/cmd/views.go @@ -4,30 +4,21 @@ import ( "fmt" "github.com/charmbracelet/lipgloss" + . "github.com/zoryia/vex/tui/pages" ) -const ( - LOGIN = "LOGIN" - REGISTER = "REGISTER" - ENTRIES = "ENTRIES" - FEEDS = "FEEDS" - TAGS = "TAGS" -) - -type VexPage string - -func (m Model) LoginView() string { +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 "" + return m.list.View() } func (m Model) FeedsView() string { - return fmt.Sprintf("%s ", *m.auth.jwt) + return fmt.Sprintf("%s ", *m.auth.Jwt) } func (m Model) TagsView() string { return "" @@ -35,15 +26,17 @@ func (m Model) TagsView() string { func (m Model) View() string { switch m.page { case LOGIN: - return m.LoginView() + return m.AuthView() case REGISTER: - return m.LoginView() + return m.AuthView() case ENTRIES: return m.EntriesView() case FEEDS: return m.FeedsView() case TAGS: return m.TagsView() + case PREVIEW: + return m.Preview.View() } - return m.textInput.View() + m.list.View() + return "Really unexpected state, get help" } diff --git a/tui/entry.go b/tui/entry.go deleted file mode 100644 index 6af4fca..0000000 --- a/tui/entry.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "time" -) - -type Feed struct { - Id string `json:"id"` - Name string `json:"name"` - Url string `json:"url"` - FaviconUrl string `json:"faviconUrl"` - Tags []string `json:"tags"` -} - -type Entry struct { - Id string `json:"id"` - ArticleTitle string `json:"title"` - Content string `json:"content"` - Link string `json:"link"` - Date time.Time `json:"time"` - - Author *string `json:"author"` // author not always specified - IsRead bool `json:"isRead"` - IsBookmarked bool `json:"IsBookmarked"` - IsIgnored bool `json:"isIgnored"` - IsReadLater bool `json:"isReadLater"` - Feed Feed `json:"feed"` -} diff --git a/tui/go.mod b/tui/go.mod index 04012b9..fe0f0a2 100644 --- a/tui/go.mod +++ b/tui/go.mod @@ -1,31 +1,48 @@ -module vex.tui +module github.com/zoryia/vex/tui go 1.22.2 require ( github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.26.1 + github.com/charmbracelet/glamour v0.7.0 github.com/charmbracelet/huh v0.3.0 + github.com/charmbracelet/lipgloss v0.10.0 + github.com/go-playground/validator/v10 v10.20.0 ) require ( + github.com/alecthomas/chroma/v2 v2.8.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/badoux/checkmail v1.2.4 // indirect github.com/catppuccin/go v0.2.0 // indirect - github.com/charmbracelet/lipgloss v0.10.0 // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/gorilla/css v1.0.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/microcosm-cc/bluemonday v1.0.25 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect + github.com/yuin/goldmark v1.5.4 // indirect + github.com/yuin/goldmark-emoji v1.0.2 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/term v0.19.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/tui/go.sum b/tui/go.sum index 319a10c..6565963 100644 --- a/tui/go.sum +++ b/tui/go.sum @@ -1,30 +1,65 @@ +github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264= +github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw= +github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= +github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/badoux/checkmail v1.2.4 h1:4zMjdYDjE2Q7xF06VNfyN8P9JGU7epLjNb+Yu5OThVI= +github.com/badoux/checkmail v1.2.4/go.mod h1:XroCOBU5zzZJcLvgwU15I+2xXyCdTWXyR9MGfRhBYy0= github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= github.com/charmbracelet/bubbletea v0.26.1 h1:xujcQeF73rh4jwu3+zhfQsvV18x+7zIjlw7/CYbzGJ0= github.com/charmbracelet/bubbletea v0.26.1/go.mod h1:FzKr7sKoO8iFVcdIBM9J0sJOcQv5nDQaYwsee3kpbgo= +github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng= +github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps= github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE= github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA= github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= +github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -33,12 +68,27 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= +github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= +github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -47,5 +97,7 @@ golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tui/models/entry.go b/tui/models/entry.go new file mode 100644 index 0000000..42cd316 --- /dev/null +++ b/tui/models/entry.go @@ -0,0 +1,83 @@ +package models + +import ( + "fmt" + "time" + + "github.com/charmbracelet/bubbles/key" +) + +type Feed struct { + Id string `json:"id"` + Name string `json:"name"` + Url string `json:"url"` + FaviconUrl string `json:"faviconUrl"` + Tags []string `json:"tags"` +} + +type Entry struct { + Id string `json:"id"` + ArticleTitle string `json:"title"` + Content string `json:"content"` + Link string `json:"link"` + Date time.Time `json:"time"` + + Author *string `json:"author"` // author not always specified + IsRead bool `json:"isRead"` + IsBookmarked bool `json:"IsBookmarked"` + IsIgnored bool `json:"isIgnored"` + IsReadLater bool `json:"isReadLater"` + Feed Feed `json:"feed"` +} + +func (e Entry) FilterValue() string { + return e.ArticleTitle +} + +func (e Entry) Title() string { + return e.ArticleTitle +} + +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("x", "ignore post"), + key.WithHelp("d", "ignore post"), + ), + ReadLaterToggle: key.NewBinding( + key.WithKeys("m"), + key.WithHelp("m", "add to read later"), + ), + } +} diff --git a/tui/auth.go b/tui/pages/auth/auth.go similarity index 53% rename from tui/auth.go rename to tui/pages/auth/auth.go index b649383..dadf8a9 100644 --- a/tui/auth.go +++ b/tui/pages/auth/auth.go @@ -1,13 +1,14 @@ -package main +package auth import ( + "github.com/badoux/checkmail" huh "github.com/charmbracelet/huh" ) -type Auth struct { - loginForm *huh.Form - registerForm *huh.Form - jwt *string +type Model struct { + LoginForm *huh.Form + RegisterForm *huh.Form + Jwt *string } func getLoginForm() *huh.Form { @@ -15,7 +16,14 @@ func getLoginForm() *huh.Form { huh.NewGroup( huh.NewInput(). Title("Email"). - Key("email"), + Key("email").Validate( + func(s string) error { + err := checkmail.ValidateFormat(s) + if err != nil { + return err + } + return nil + }), huh.NewInput(). Title("Password"). Key("password"). @@ -28,7 +36,14 @@ func getRegisterForm() *huh.Form { huh.NewGroup( huh.NewInput(). Title("Email"). - Key("email"), + Key("email").Validate( + func(s string) error { + err := checkmail.ValidateFormat(s) + if err != nil { + return err + } + return nil + }), huh.NewInput(). Title("Username"). Key("username"), @@ -42,3 +57,8 @@ func getRegisterForm() *huh.Form { Password(true), )).WithWidth(40) } + +func New() Model { + return Model{RegisterForm: getRegisterForm(), LoginForm: getLoginForm(), Jwt: new(string)} + +} diff --git a/tui/pages/pages.go b/tui/pages/pages.go new file mode 100644 index 0000000..2984f4a --- /dev/null +++ b/tui/pages/pages.go @@ -0,0 +1,13 @@ +package pages + +const ( + LOGIN = "LOGIN" + REGISTER = "REGISTER" + ENTRIES = "ENTRIES" + FEEDS = "FEEDS" + TAGS = "TAGS" + IGNORED = "IGNORED" + PREVIEW = "PREVIEW" +) + +type VexPage string diff --git a/tui/pages/preview/model.go b/tui/pages/preview/model.go new file mode 100644 index 0000000..860bac9 --- /dev/null +++ b/tui/pages/preview/model.go @@ -0,0 +1,88 @@ +package preview + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" + "github.com/zoryia/vex/tui/models" + + "errors" +) + +type Model struct { + Entry models.Entry + Viewport viewport.Model +} + +// RenderMarkdown renders the markdown content with glamour. +func RenderMarkdown(width int, content string) (string, error) { + background := "light" + + if lipgloss.HasDarkBackground() { + background = "dark" + } + + r, _ := glamour.NewTermRenderer( + glamour.WithWordWrap(width), + glamour.WithStandardStyle(background), + ) + + out, err := r.Render(content) + if err != nil { + return "", errors.Unwrap(err) + } + + return out, nil +} + +func renderMarkdownCmd(width int, entry models.Entry) tea.Cmd { + return func() tea.Msg { + _, err := RenderMarkdown(width, entry.Content) + if err != nil { + //return errorMsg(err) + } + return nil + // return renderMarkdownMsg(markdownContent) + } +} + +var ( + titleStyle = func() lipgloss.Style { + b := lipgloss.RoundedBorder() + b.Right = "├" + return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) + }() + + infoStyle = func() lipgloss.Style { + b := lipgloss.RoundedBorder() + b.Left = "┤" + return titleStyle.Copy().BorderStyle(b) + }() +) + +// TODO: render as markdown with glamour +func (m Model) View() string { + return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.Viewport.View(), m.footerView()) +} + +func (m Model) VerticalMarginHeight() int { + headerHeight := lipgloss.Height(m.headerView()) + footerHeight := lipgloss.Height(m.footerView()) + return headerHeight + footerHeight +} + +func (m Model) headerView() string { + title := titleStyle.Render(m.Entry.ArticleTitle) + line := strings.Repeat("─", max(0, m.Viewport.Width-lipgloss.Width(title))) + return lipgloss.JoinHorizontal(lipgloss.Center, title, line) +} + +func (m Model) footerView() string { + info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.Viewport.ScrollPercent()*100)) + line := strings.Repeat("─", max(0, m.Viewport.Width-lipgloss.Width(info))) + return lipgloss.JoinHorizontal(lipgloss.Center, line, info) +} From 03b3566b0ae679f93092d138008e4e90eae6add4 Mon Sep 17 00:00:00 2001 From: GitBluub Date: Sun, 5 May 2024 16:22:03 +0200 Subject: [PATCH 2/6] keybinds in entries list --- tui/cmd/http.go | 48 ++++++++++++++++++++++++++++++++++++++++ tui/cmd/main.go | 53 +++++++++++++++++++++++---------------------- tui/cmd/views.go | 2 +- tui/go.mod | 9 ++------ tui/go.sum | 24 ++------------------ tui/models/entry.go | 13 ++++++----- 6 files changed, 87 insertions(+), 62 deletions(-) diff --git a/tui/cmd/http.go b/tui/cmd/http.go index 696ae50..f90f8a4 100644 --- a/tui/cmd/http.go +++ b/tui/cmd/http.go @@ -9,6 +9,7 @@ import ( "net/http" tea "github.com/charmbracelet/bubbletea" + "github.com/google/uuid" "github.com/zoryia/vex/tui/models" ) @@ -180,3 +181,50 @@ func getFeeds(jwt *string) tea.Cmd { return getFeedsSuccessMsg(feeds) } } + +type ignorePostSuccessMsg uuid.UUID + +func ignorePost(jwt *string, e models.Entry) tea.Cmd { + return func() tea.Msg { + url := fmt.Sprintf("%s/ignore/%s", serverUrl, e.Id.String()) + req, _ := http.NewRequest(http.MethodPut, url, nil) + if jwt == nil { + return missingJwtMsg{} + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", *jwt)) + data, err := getData(req) + if err != nil { + return httpErrorMsg(err) + } + var feeds []models.Feed + err = json.Unmarshal(data, &feeds) + if err != nil { + return httpErrorMsg(err) + } + return getFeedsSuccessMsg(feeds) + } +} + +type toggleReadSuccessMsg uuid.UUID + +func toggleRead(jwt *string, e models.Entry) tea.Cmd { + return func() tea.Msg { + return nil + } +} + +type toggleReadLaterSuccessMsg uuid.UUID + +func toggleReadLater(jwt *string, e models.Entry) tea.Cmd { + return func() tea.Msg { + return nil + } +} + +type toggleBookmarkSuccessMsg uuid.UUID + +func toggleBookmark(jwt *string, e models.Entry) tea.Cmd { + return func() tea.Msg { + return nil + } +} diff --git a/tui/cmd/main.go b/tui/cmd/main.go index 381c380..25f7290 100644 --- a/tui/cmd/main.go +++ b/tui/cmd/main.go @@ -1,8 +1,6 @@ package main import ( - "log" - _ "log" "os" "strings" "time" @@ -13,6 +11,7 @@ import ( "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" @@ -36,7 +35,6 @@ type Model struct { func New() *Model { ti := textinput.New() ti.Placeholder = "Pikachu" - ti.Focus() 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)}} @@ -52,11 +50,11 @@ 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) - var f = Feed{Id: "1", Tags: []string{"Devops", "Kubernetes"}, Name: "zwindler", Url: "zwindler.blog", FaviconUrl: "zwindler.blog.favicon"} + var f = Feed{Id: uuid.UUID{}, Tags: []string{"Devops", "Kubernetes"}, Name: "zwindler", Url: "zwindler.blog", FaviconUrl: "zwindler.blog.favicon"} m.list.SetItems([]list.Item{ - Entry{Id: "1", ArticleTitle: "yay", Content: "ouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, - Entry{Id: "2", ArticleTitle: "grrrrr", Content: "ouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, - Entry{Id: "3", ArticleTitle: "my life is pain", Content: "ouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, + Entry{Id: uuid.UUID{}, ArticleTitle: "yay", Content: "ouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, + Entry{Id: uuid.UUID{}, ArticleTitle: "grrrrr", Content: "ouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, + Entry{Id: uuid.UUID{}, ArticleTitle: "my life is pain", Content: "ouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, }) } @@ -150,6 +148,7 @@ func (m *Model) deleteWordBackward() { 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) @@ -180,7 +179,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } 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 @@ -189,35 +193,32 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.deleteWordBackward() } case key.Matches(msg, m.keys.IgnoreToggle) && m.page == "FEEDS": - // TODO: ignore the post - return m, nil + var e = m.list.SelectedItem().(Entry) + cmds = append(cmds, ignorePost(m.auth.Jwt, e)) case key.Matches(msg, m.keys.ReadToggle): - // TODO: mark as read - return m, nil + var e = m.list.SelectedItem().(Entry) + cmds = append(cmds, toggleRead(m.auth.Jwt, e)) case key.Matches(msg, m.keys.ReadLaterToggle): - // TODO: add to read later - return m, nil + var e = m.list.SelectedItem().(Entry) + cmds = append(cmds, toggleReadLater(m.auth.Jwt, e)) case key.Matches(msg, m.keys.BookmarkToggle): - // TODO: toggle bookmark - return m, nil + var e = m.list.SelectedItem().(Entry) + cmds = append(cmds, toggleBookmark(m.auth.Jwt, e)) case key.Matches(msg, m.keys.Query): - // TODO: launch query input - return m, nil + m.textInput.Focus() + m.textInput.SetValue("") - case key.Matches(msg, m.keys.PreviewPost): - var e = m.list.SelectedItem() - - entry := e.(Entry) - m.Preview.Entry = entry - m.Preview.Viewport.SetContent(entry.Content) + 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 - log.Print(entry.Content) - log.Print(m.Preview.Viewport.VisibleLineCount()) } + } // Process the form diff --git a/tui/cmd/views.go b/tui/cmd/views.go index 319ed8f..a5e6b22 100644 --- a/tui/cmd/views.go +++ b/tui/cmd/views.go @@ -15,7 +15,7 @@ func (m Model) AuthView() string { ) } func (m Model) EntriesView() string { - return m.list.View() + return m.textInput.View() + m.list.View() } func (m Model) FeedsView() string { return fmt.Sprintf("%s ", *m.auth.Jwt) diff --git a/tui/go.mod b/tui/go.mod index fe0f0a2..c199caa 100644 --- a/tui/go.mod +++ b/tui/go.mod @@ -3,12 +3,13 @@ module github.com/zoryia/vex/tui go 1.22.2 require ( + github.com/badoux/checkmail v1.2.4 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.26.1 github.com/charmbracelet/glamour v0.7.0 github.com/charmbracelet/huh v0.3.0 github.com/charmbracelet/lipgloss v0.10.0 - github.com/go-playground/validator/v10 v10.20.0 + github.com/google/uuid v1.6.0 ) require ( @@ -16,15 +17,10 @@ require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/badoux/checkmail v1.2.4 // indirect github.com/catppuccin/go v0.2.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect github.com/gorilla/css v1.0.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -39,7 +35,6 @@ require ( github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect github.com/yuin/goldmark v1.5.4 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect - golang.org/x/crypto v0.19.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.19.0 // indirect diff --git a/tui/go.sum b/tui/go.sum index 6565963..bc59410 100644 --- a/tui/go.sum +++ b/tui/go.sum @@ -24,30 +24,18 @@ github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0Xs github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA= github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -70,23 +58,17 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= @@ -99,5 +81,3 @@ golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tui/models/entry.go b/tui/models/entry.go index 42cd316..a6cb292 100644 --- a/tui/models/entry.go +++ b/tui/models/entry.go @@ -5,18 +5,19 @@ import ( "time" "github.com/charmbracelet/bubbles/key" + "github.com/google/uuid" ) type Feed struct { - Id string `json:"id"` - Name string `json:"name"` - Url string `json:"url"` - FaviconUrl string `json:"faviconUrl"` - Tags []string `json:"tags"` + 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 string `json:"id"` + Id uuid.UUID `json:"id"` ArticleTitle string `json:"title"` Content string `json:"content"` Link string `json:"link"` From 76a8209b9049d1e8172ba3e155c6d52f69f67a3c Mon Sep 17 00:00:00 2001 From: GitBluub Date: Sun, 5 May 2024 16:39:19 +0200 Subject: [PATCH 3/6] keybinds in help --- tui/cmd/main.go | 21 +++++++++++++++++++-- tui/cmd/views.go | 2 +- tui/models/entry.go | 3 +-- tui/pages/feeds/model.go | 0 4 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 tui/pages/feeds/model.go diff --git a/tui/cmd/main.go b/tui/cmd/main.go index 25f7290..fda3b51 100644 --- a/tui/cmd/main.go +++ b/tui/cmd/main.go @@ -34,7 +34,7 @@ type Model struct { func New() *Model { ti := textinput.New() - ti.Placeholder = "Pikachu" + 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)}} @@ -50,6 +50,22 @@ 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.AdditionalShortHelpKeys = func() []key.Binding { + return []key.Binding{ + m.keys.Query, + m.keys.PreviewPost, + } + } + m.list.AdditionalFullHelpKeys = func() []key.Binding { + return []key.Binding{ + m.keys.ReadToggle, + m.keys.ReadLaterToggle, + m.keys.BookmarkToggle, + m.keys.IgnoreToggle, + m.keys.Query, + m.keys.PreviewPost, + } + } var f = Feed{Id: uuid.UUID{}, Tags: []string{"Devops", "Kubernetes"}, Name: "zwindler", Url: "zwindler.blog", FaviconUrl: "zwindler.blog.favicon"} m.list.SetItems([]list.Item{ Entry{Id: uuid.UUID{}, ArticleTitle: "yay", Content: "ouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouinouin ouin ouin", Link: "awd", Date: time.Now(), IsRead: false, IsIgnored: false, IsReadLater: false, IsBookmarked: false, Feed: f}, @@ -151,7 +167,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var blurredNow = false switch msg := msg.(type) { case tea.WindowSizeMsg: - m.initList(msg.Width, msg.Height) + 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() diff --git a/tui/cmd/views.go b/tui/cmd/views.go index a5e6b22..94d0889 100644 --- a/tui/cmd/views.go +++ b/tui/cmd/views.go @@ -15,7 +15,7 @@ func (m Model) AuthView() string { ) } func (m Model) EntriesView() string { - return m.textInput.View() + m.list.View() + return lipgloss.JoinVertical(lipgloss.Left, m.textInput.View(), m.list.View()) } func (m Model) FeedsView() string { return fmt.Sprintf("%s ", *m.auth.Jwt) diff --git a/tui/models/entry.go b/tui/models/entry.go index a6cb292..d9a6d1f 100644 --- a/tui/models/entry.go +++ b/tui/models/entry.go @@ -73,8 +73,7 @@ func NewListKeyMap() *ListKeyMap { IgnoreToggle: key.NewBinding( key.WithKeys("x", "d"), - key.WithHelp("x", "ignore post"), - key.WithHelp("d", "ignore post"), + key.WithHelp("d/x", "ignore post"), ), ReadLaterToggle: key.NewBinding( key.WithKeys("m"), diff --git a/tui/pages/feeds/model.go b/tui/pages/feeds/model.go new file mode 100644 index 0000000..e69de29 From bed51528994bd57de2d7df2e34f3e6aac031f2d3 Mon Sep 17 00:00:00 2001 From: GitBluub Date: Sun, 5 May 2024 18:51:54 +0200 Subject: [PATCH 4/6] 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{})} + +} From 5618eda53016a2225f68b2b8b61a49ca631647bd Mon Sep 17 00:00:00 2001 From: GitBluub Date: Sun, 5 May 2024 19:44:27 +0200 Subject: [PATCH 5/6] getting entries --- tui/cmd/http.go | 47 +++++++++++++++++++++++++++++++++++++- tui/cmd/main.go | 4 +--- tui/cmd/update.go | 36 +++++++++++++++++++++++++++-- tui/models/entry.go | 14 ++++++------ tui/models/feed.go | 13 +++++++---- tui/pages/feeds/keymaps.go | 21 +++++++++++++++++ tui/pages/feeds/model.go | 31 ++++++++++--------------- 7 files changed, 129 insertions(+), 37 deletions(-) create mode 100644 tui/pages/feeds/keymaps.go diff --git a/tui/cmd/http.go b/tui/cmd/http.go index f90f8a4..99297e2 100644 --- a/tui/cmd/http.go +++ b/tui/cmd/http.go @@ -138,23 +138,36 @@ func register(username string, password string, email string) tea.Cmd { type getEntriesSuccessMsg []models.Entry func getEntries(jwt *string) tea.Cmd { + log.Print("yey") return func() tea.Msg { + log.Print("oula") url := fmt.Sprintf("%s/entries", serverUrl) - req, _ := http.NewRequest(http.MethodGet, url, nil) + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + log.Print(err) + return http.ErrMissingBoundary + } if jwt == nil { + log.Print("uhu?") return missingJwtMsg{} } req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", *jwt)) req.Header.Add("Content-type", "application/json") + log.Print("b") data, err := getData(req) + log.Print("c") if err != nil { + log.Print(err) return httpErrorMsg(err) } var entries []models.Entry err = json.Unmarshal(data, &entries) if err != nil { + log.Print(err) return httpErrorMsg(err) } + log.Print("a") + log.Print(entries) return getEntriesSuccessMsg(entries) } } @@ -182,6 +195,38 @@ func getFeeds(jwt *string) tea.Cmd { } } +type addFeedSuccessMsg struct{} + +func addFeed(jwt *string, link string, tags []string) tea.Cmd { + return func() tea.Msg { + + url := fmt.Sprintf("%s/feeds", serverUrl) + body := struct { + Link string `json:"link"` + Tags []string `json:"tags"` + }{ + Link: link, Tags: tags, + } + out, err := json.Marshal(body) + if err != nil { + log.Fatal(err) + } + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(out)) + req.Header.Add("Content-type", "application/json") + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", *jwt)) + data, err := getData(req) + if err != nil { + return err + } + var addFeedResp AuthRes + err = json.Unmarshal(data, &addFeedResp) + if err != nil { + return httpErrorMsg(err) + } + return addFeedSuccessMsg{} + } +} + type ignorePostSuccessMsg uuid.UUID func ignorePost(jwt *string, e models.Entry) tea.Cmd { diff --git a/tui/cmd/main.go b/tui/cmd/main.go index 5853b0c..8e2368a 100644 --- a/tui/cmd/main.go +++ b/tui/cmd/main.go @@ -54,9 +54,7 @@ func New() *Model { } 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) } func (m *Model) initList(width int, height int) { diff --git a/tui/cmd/update.go b/tui/cmd/update.go index c0da9fe..228b465 100644 --- a/tui/cmd/update.go +++ b/tui/cmd/update.go @@ -4,11 +4,12 @@ import ( "strings" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" huh "github.com/charmbracelet/huh" - . "github.com/zoryia/vex/tui/models" . "github.com/zoryia/vex/tui/pages" + "github.com/zoryia/vex/tui/pages/feeds" ) func (m Model) LoginUpdate(msg tea.Msg) (tea.Model, []tea.Cmd) { @@ -29,6 +30,13 @@ func (m Model) GlobalUpdate(msg tea.Msg) (Model, tea.Cmd) { m.Feeds.List.SetWidth(msg.Width) m.Feeds.List.SetHeight(msg.Height) + case getEntriesSuccessMsg: + var entries []list.Item + for _, e := range msg { + entries = append(entries, e) + } + m.list.SetItems(entries) + case invalidJwtMsg: m.Auth.Jwt = new(string) m.page = LOGIN @@ -170,7 +178,31 @@ func entriesUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) { return m, tea.Batch(cmds...) } func feedsUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) { - return m, nil + var cmds []tea.Cmd + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, feeds.FeedsKeyMaps().AddFeed): + m.Feeds.AddFeed.Run() + } + } + 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 { + url := m.Auth.RegisterForm.GetString("url") + tags := m.Auth.RegisterForm.Get("tags").([]string) + cmds = append(cmds, addFeed(m.Auth.Jwt, url, tags)) + } + m.Feeds.List, cmd = m.Feeds.List.Update(msg) + cmds = append(cmds, cmd) + + return m, tea.Batch(cmds...) } func tagsUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) { return m, nil diff --git a/tui/models/entry.go b/tui/models/entry.go index 625bd77..ae33fbe 100644 --- a/tui/models/entry.go +++ b/tui/models/entry.go @@ -12,14 +12,14 @@ type Entry struct { ArticleTitle string `json:"title"` Content string `json:"content"` Link string `json:"link"` - Date time.Time `json:"time"` + Date time.Time `json:"date"` - Author *string `json:"author"` // author not always specified - IsRead bool `json:"isRead"` - IsBookmarked bool `json:"IsBookmarked"` - IsIgnored bool `json:"isIgnored"` - IsReadLater bool `json:"isReadLater"` - Feed Feed `json:"feed"` + Authors []string `json:"authors"` // author not always specified + IsRead bool `json:"isRead"` + IsBookmarked bool `json:"IsBookmarked"` + IsIgnored bool `json:"isIgnored"` + IsReadLater bool `json:"isReadLater"` + Feed Feed `json:"feed"` } func (e Entry) FilterValue() string { diff --git a/tui/models/feed.go b/tui/models/feed.go index baae1db..c947d6b 100644 --- a/tui/models/feed.go +++ b/tui/models/feed.go @@ -2,16 +2,19 @@ package models import ( "fmt" + "time" "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"` + Id uuid.UUID `json:"id"` + Name string `json:"name"` + Url string `json:"link"` + FaviconUrl string `json:"faviconUrl"` + Tags []string `json:"tags"` + AddedDate time.Time `json:"addedDate"` + SubmitterId uuid.UUID `json:"submitterId"` } func (f Feed) FilterValue() string { diff --git a/tui/pages/feeds/keymaps.go b/tui/pages/feeds/keymaps.go new file mode 100644 index 0000000..bab8407 --- /dev/null +++ b/tui/pages/feeds/keymaps.go @@ -0,0 +1,21 @@ +package feeds + +import "github.com/charmbracelet/bubbles/key" + +type ListKeyMap struct { + GoToPosts key.Binding + AddFeed key.Binding +} + +func FeedsKeyMaps() *ListKeyMap { + return &ListKeyMap{ + GoToPosts: key.NewBinding( + key.WithKeys("p"), + key.WithHelp("p", "Go to posts"), + ), + AddFeed: key.NewBinding( + key.WithKeys("a"), + key.WithHelp("a", "Add feed"), + ), + } +} diff --git a/tui/pages/feeds/model.go b/tui/pages/feeds/model.go index be4060f..0b51e08 100644 --- a/tui/pages/feeds/model.go +++ b/tui/pages/feeds/model.go @@ -18,30 +18,23 @@ 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.AdditionalShortHelpKeys = func() []key.Binding { + return []key.Binding{ + FeedsKeyMaps().AddFeed, + FeedsKeyMaps().GoToPosts, + } + } + feeds.AdditionalFullHelpKeys = func() []key.Binding { + return []key.Binding{ + FeedsKeyMaps().AddFeed, + FeedsKeyMaps().GoToPosts, + } + } 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"}, From 37b9755134915d35e416da38c599c1e80352eccc Mon Sep 17 00:00:00 2001 From: GitBluub Date: Sun, 5 May 2024 19:55:25 +0200 Subject: [PATCH 6/6] remove debug --- tui/cmd/http.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tui/cmd/http.go b/tui/cmd/http.go index 99297e2..26f24c6 100644 --- a/tui/cmd/http.go +++ b/tui/cmd/http.go @@ -138,9 +138,7 @@ func register(username string, password string, email string) tea.Cmd { type getEntriesSuccessMsg []models.Entry func getEntries(jwt *string) tea.Cmd { - log.Print("yey") return func() tea.Msg { - log.Print("oula") url := fmt.Sprintf("%s/entries", serverUrl) req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { @@ -148,14 +146,11 @@ func getEntries(jwt *string) tea.Cmd { return http.ErrMissingBoundary } if jwt == nil { - log.Print("uhu?") return missingJwtMsg{} } req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", *jwt)) req.Header.Add("Content-type", "application/json") - log.Print("b") data, err := getData(req) - log.Print("c") if err != nil { log.Print(err) return httpErrorMsg(err) @@ -166,8 +161,6 @@ func getEntries(jwt *string) tea.Cmd { log.Print(err) return httpErrorMsg(err) } - log.Print("a") - log.Print(entries) return getEntriesSuccessMsg(entries) } }