mirror of
https://github.com/zoriya/vex.git
synced 2026-06-07 12:15:35 +00:00
restructure and email validation
This commit is contained in:
@@ -0,0 +1 @@
|
||||
vex.log
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
+20
-3
@@ -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
|
||||
)
|
||||
|
||||
+54
-2
@@ -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=
|
||||
|
||||
@@ -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"),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -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)}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package pages
|
||||
|
||||
const (
|
||||
LOGIN = "LOGIN"
|
||||
REGISTER = "REGISTER"
|
||||
ENTRIES = "ENTRIES"
|
||||
FEEDS = "FEEDS"
|
||||
TAGS = "TAGS"
|
||||
IGNORED = "IGNORED"
|
||||
PREVIEW = "PREVIEW"
|
||||
)
|
||||
|
||||
type VexPage string
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user