mirror of
https://github.com/zoriya/vex.git
synced 2026-05-22 22:46:25 +00:00
Merge pull request #5 from zoriya/tui
architecture basis with start of auth page
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
import huh "github.com/charmbracelet/huh"
|
||||
|
||||
type Auth struct {
|
||||
form *huh.Form
|
||||
jwt *string
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type Feed struct {
|
||||
@@ -31,62 +26,3 @@ type Entry struct {
|
||||
isReadLater bool
|
||||
feed Feed
|
||||
}
|
||||
|
||||
func (e Entry) FilterValue() string {
|
||||
return e.title
|
||||
}
|
||||
|
||||
func (e Entry) Title() string {
|
||||
return e.title
|
||||
}
|
||||
|
||||
func (e Entry) Description() string {
|
||||
return fmt.Sprintf("%s", "my desc") // TODO: real description
|
||||
}
|
||||
|
||||
type Model struct {
|
||||
list list.Model
|
||||
err error
|
||||
}
|
||||
|
||||
func New() *Model {
|
||||
return &Model{}
|
||||
}
|
||||
|
||||
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"}
|
||||
m.list.SetItems([]list.Item{
|
||||
Entry{id: "1", title: "yay", content: "ouin ouin ouin", link: "awd", date: time.Now(), isRead: false, isIgnored: false, isReadLater: false, isBookmarked: false, feed: f},
|
||||
Entry{id: "2", title: "grrrrr", content: "ouin ouin ouin", link: "awd", date: time.Now(), isRead: false, isIgnored: false, isReadLater: false, isBookmarked: false, feed: f},
|
||||
Entry{id: "3", title: "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 nil
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.initList(msg.Width, msg.Height)
|
||||
}
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
return m.list.View()
|
||||
}
|
||||
|
||||
func main() {
|
||||
m := New()
|
||||
p := tea.NewProgram(m)
|
||||
if _, err := p.Run(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -5,15 +5,17 @@ go 1.22.2
|
||||
require (
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.26.1
|
||||
github.com/charmbracelet/lipgloss v0.10.0
|
||||
github.com/charmbracelet/huh v0.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/catppuccin/go v0.2.0 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.10.0 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // 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/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
@@ -25,5 +27,5 @@ require (
|
||||
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.3.8 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
)
|
||||
|
||||
+8
-4
@@ -2,10 +2,14 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
|
||||
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/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/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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
@@ -14,8 +18,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
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.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
@@ -43,5 +47,5 @@ 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.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
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=
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type statusMsg int
|
||||
|
||||
type errMsg struct{ error }
|
||||
type missingJwtMsg struct{}
|
||||
|
||||
func (e errMsg) Error() string { return e.error.Error() }
|
||||
|
||||
type getEntriesSuccessMsg []Entry
|
||||
type getEntriesErrMsg error
|
||||
|
||||
const serverUrl = "localhost:3000"
|
||||
|
||||
type loginSuccessMsg struct{ string }
|
||||
|
||||
func login(username string, password string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
_ = username
|
||||
_ = password
|
||||
return loginSuccessMsg{"dawdaw"}
|
||||
}
|
||||
}
|
||||
|
||||
func getEntries(jwt *string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
url := fmt.Sprintf("%s/entries", serverUrl)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if jwt == nil {
|
||||
return missingJwtMsg{}
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", *jwt))
|
||||
|
||||
if err != nil {
|
||||
return getEntriesErrMsg(err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return getEntriesErrMsg(err)
|
||||
}
|
||||
defer resp.Body.Close() // nolint: errcheck
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return getEntriesErrMsg(err)
|
||||
}
|
||||
|
||||
var entries []Entry
|
||||
|
||||
err = json.Unmarshal(data, &entries)
|
||||
if err != nil {
|
||||
return getEntriesErrMsg(err)
|
||||
}
|
||||
|
||||
return getEntriesSuccessMsg(entries)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type getFeedsSuccessMsg []Feed
|
||||
type getFeedsErrMsg error
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
huh "github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
func (e Entry) FilterValue() string {
|
||||
return e.title
|
||||
}
|
||||
|
||||
func (e Entry) Title() string {
|
||||
return e.title
|
||||
}
|
||||
|
||||
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
|
||||
page VexPage
|
||||
query string
|
||||
feeds []Feed
|
||||
entries []Entry
|
||||
tags []string
|
||||
}
|
||||
|
||||
func New() *Model {
|
||||
loginForm := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().
|
||||
Title("Username").
|
||||
Key("username"),
|
||||
// Validating fields is easy. The form will mark erroneous fields
|
||||
// and display error messages accordingly.
|
||||
// TODO:
|
||||
// Validate(func(str string) error {
|
||||
// if str == "octopus773" {
|
||||
// return errors.New("Not you")
|
||||
// }
|
||||
// return nil
|
||||
// })))
|
||||
huh.NewInput().
|
||||
Title("Password").
|
||||
Key("password").
|
||||
Password(true),
|
||||
))
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = "Pikachu"
|
||||
ti.Focus()
|
||||
ti.CharLimit = 156
|
||||
ti.Width = 56
|
||||
return &Model{textInput: ti, auth: Auth{form: loginForm, jwt: new(string)}, page: "LOGIN"}
|
||||
}
|
||||
|
||||
func (m Model) getEverything() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return tea.Batch(getEntries(m.auth.jwt)) // getTags, getFeeds)
|
||||
}
|
||||
}
|
||||
|
||||
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"}
|
||||
m.list.SetItems([]list.Item{
|
||||
Entry{id: "1", title: "yay", content: "ouin ouin ouin", link: "awd", date: time.Now(), isRead: false, isIgnored: false, isReadLater: false, isBookmarked: false, feed: f},
|
||||
Entry{id: "2", title: "grrrrr", content: "ouin ouin ouin", link: "awd", date: time.Now(), isRead: false, isIgnored: false, isReadLater: false, isBookmarked: false, feed: f},
|
||||
Entry{id: "3", title: "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.form.Init())
|
||||
|
||||
}
|
||||
|
||||
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:])
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.initList(msg.Width, msg.Height)
|
||||
case loginSuccessMsg:
|
||||
*m.auth.jwt = msg.string
|
||||
m.page = FEEDS
|
||||
|
||||
case tea.KeyMsg:
|
||||
switch msg.Type {
|
||||
case tea.KeyCtrlC, tea.KeyEsc:
|
||||
return m, tea.Quit
|
||||
}
|
||||
switch {
|
||||
case key.Matches(msg, m.textInput.KeyMap.DeleteCharacterBackward):
|
||||
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()
|
||||
}
|
||||
}
|
||||
// if writing
|
||||
}
|
||||
var cmds []tea.Cmd
|
||||
|
||||
// Process the form
|
||||
form, cmd := m.auth.form.Update(msg)
|
||||
if f, ok := form.(*huh.Form); ok {
|
||||
m.auth.form = f
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
if m.auth.form.State == huh.StateCompleted {
|
||||
// Quit when the form is done.
|
||||
username := m.auth.form.GetString("username")
|
||||
password := m.auth.form.GetString("password")
|
||||
cmds = append(cmds, login(username, password))
|
||||
}
|
||||
|
||||
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() {
|
||||
tea.LogToFile("yay.log", "")
|
||||
m := New()
|
||||
p := tea.NewProgram(m)
|
||||
if _, err := p.Run(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
const (
|
||||
LOGIN = "LOGIN"
|
||||
ENTRIES = "ENTRIES"
|
||||
FEEDS = "FEEDS"
|
||||
TAGS = "TAGS"
|
||||
)
|
||||
|
||||
type VexPage string
|
||||
|
||||
func (m Model) LoginView() string {
|
||||
return m.auth.form.View()
|
||||
}
|
||||
func (m Model) EntriesView() string {
|
||||
return ""
|
||||
}
|
||||
func (m Model) FeedsView() string {
|
||||
return "feeds"
|
||||
}
|
||||
func (m Model) TagsView() string {
|
||||
return ""
|
||||
}
|
||||
func (m Model) View() string {
|
||||
switch m.page {
|
||||
case LOGIN:
|
||||
return m.LoginView()
|
||||
case ENTRIES:
|
||||
return m.EntriesView()
|
||||
case FEEDS:
|
||||
return m.FeedsView()
|
||||
case TAGS:
|
||||
return m.TagsView()
|
||||
}
|
||||
return m.textInput.View() + m.list.View()
|
||||
}
|
||||
Reference in New Issue
Block a user