mirror of
https://github.com/zoriya/vex.git
synced 2026-06-07 12:15:35 +00:00
big refacto
This commit is contained in:
@@ -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"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
-229
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/key"
|
"github.com/charmbracelet/bubbles/key"
|
||||||
@@ -10,39 +9,53 @@ import (
|
|||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
huh "github.com/charmbracelet/huh"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
. "github.com/zoryia/vex/tui/models"
|
. "github.com/zoryia/vex/tui/models"
|
||||||
. "github.com/zoryia/vex/tui/pages"
|
. "github.com/zoryia/vex/tui/pages"
|
||||||
"github.com/zoryia/vex/tui/pages/auth"
|
"github.com/zoryia/vex/tui/pages/auth"
|
||||||
|
"github.com/zoryia/vex/tui/pages/feeds"
|
||||||
"github.com/zoryia/vex/tui/pages/preview"
|
"github.com/zoryia/vex/tui/pages/preview"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
list list.Model
|
// entries
|
||||||
textInput textinput.Model
|
list list.Model
|
||||||
err error
|
queryInput textinput.Model
|
||||||
auth auth.Model
|
|
||||||
page VexPage
|
err error
|
||||||
query string
|
page VexPage
|
||||||
feeds []Feed
|
tags []string
|
||||||
entries []Entry
|
keys *ListKeyMap
|
||||||
tags []string
|
|
||||||
keys *ListKeyMap
|
Preview preview.Model
|
||||||
Preview preview.Model
|
Feeds feeds.Model
|
||||||
|
Auth auth.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Model {
|
func queryInput() textinput.Model {
|
||||||
ti := textinput.New()
|
ti := textinput.New()
|
||||||
ti.Placeholder = "Search Query"
|
ti.Placeholder = "Search Query"
|
||||||
ti.CharLimit = 156
|
ti.CharLimit = 156
|
||||||
ti.Width = 56
|
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 {
|
func (m Model) getEverything() tea.Cmd {
|
||||||
return func() tea.Msg {
|
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 = list.New([]list.Item{}, list.NewDefaultDelegate(), width, height)
|
||||||
m.list.Title = "Posts"
|
m.list.Title = "Posts"
|
||||||
m.list.SetFilteringEnabled(false)
|
m.list.SetFilteringEnabled(false)
|
||||||
|
m.list.DisableQuitKeybindings()
|
||||||
m.list.AdditionalShortHelpKeys = func() []key.Binding {
|
m.list.AdditionalShortHelpKeys = func() []key.Binding {
|
||||||
return []key.Binding{
|
return []key.Binding{
|
||||||
m.keys.Query,
|
m.keys.Query,
|
||||||
@@ -58,6 +72,7 @@ func (m *Model) initList(width int, height int) {
|
|||||||
}
|
}
|
||||||
m.list.AdditionalFullHelpKeys = func() []key.Binding {
|
m.list.AdditionalFullHelpKeys = func() []key.Binding {
|
||||||
return []key.Binding{
|
return []key.Binding{
|
||||||
|
m.keys.GoToFeeds,
|
||||||
m.keys.ReadToggle,
|
m.keys.ReadToggle,
|
||||||
m.keys.ReadLaterToggle,
|
m.keys.ReadLaterToggle,
|
||||||
m.keys.BookmarkToggle,
|
m.keys.BookmarkToggle,
|
||||||
@@ -75,219 +90,11 @@ func (m *Model) initList(width int, height int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) Init() tea.Cmd {
|
func (m Model) Init() tea.Cmd {
|
||||||
return tea.Batch(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),
|
||||||
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...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -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:])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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...)
|
||||||
|
}
|
||||||
+4
-6
@@ -1,8 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
. "github.com/zoryia/vex/tui/pages"
|
. "github.com/zoryia/vex/tui/pages"
|
||||||
)
|
)
|
||||||
@@ -10,15 +8,15 @@ import (
|
|||||||
func (m Model) AuthView() string {
|
func (m Model) AuthView() string {
|
||||||
return lipgloss.JoinHorizontal(
|
return lipgloss.JoinHorizontal(
|
||||||
lipgloss.Left,
|
lipgloss.Left,
|
||||||
m.auth.LoginForm.View(),
|
m.Auth.LoginForm.View(),
|
||||||
m.auth.RegisterForm.View(),
|
m.Auth.RegisterForm.View(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
func (m Model) EntriesView() string {
|
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 {
|
func (m Model) FeedsView() string {
|
||||||
return fmt.Sprintf("%s ", *m.auth.Jwt)
|
return m.Feeds.View()
|
||||||
}
|
}
|
||||||
func (m Model) TagsView() string {
|
func (m Model) TagsView() string {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -4,18 +4,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/key"
|
|
||||||
"github.com/google/uuid"
|
"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 {
|
type Entry struct {
|
||||||
Id uuid.UUID `json:"id"`
|
Id uuid.UUID `json:"id"`
|
||||||
ArticleTitle string `json:"title"`
|
ArticleTitle string `json:"title"`
|
||||||
@@ -42,42 +33,3 @@ func (e Entry) Title() string {
|
|||||||
func (e Entry) Description() string {
|
func (e Entry) Description() string {
|
||||||
return fmt.Sprintf("%s", "my desc") // TODO: real description (tags and author + date ?)
|
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"),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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{})}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user