diff --git a/README.md b/README.md
index 5e7d2fd..44fd79c 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,14 @@
# tofui
-tofui (Terminally On Farcaster User Interface) is a TUI for [farcaster](https://www.farcaster.xyz/).
+tofui (Terminally On Farcaster User Interface) is a TUI for
+[farcaster](https://www.farcaster.xyz/).
-It supports running locally using your own [Neynar](https://neynar.com/) application, or as a hosted SSH app using [wish](https://github.com/charmbracelet/wish).
+It supports running locally using your own [Neynar](https://neynar.com/)
+application, or as a hosted SSH app using
+[wish](https://github.com/charmbracelet/wish).
![tofui screenshot](./media/screenshot.png)
-
## Hosted version
Use a hosted instance of tofui over ssh
@@ -19,36 +21,51 @@ ssh -p 42069 tofui.xyz
### SSH Sessions, Authentication and Details
-Each SSH session is authenticated via it's SSH public key. The session then receives it's own [Bubble Tea](https://github.com/charmbracelet/bubbletea) which provides the interface.
+Each SSH session is authenticated via it's SSH public key. The session then
+receives it's own [Bubble Tea](https://github.com/charmbracelet/bubbletea) which
+provides the interface.
-For authorization, the app directs you to create a signer via Neynar's [SIWN](https://docs.neynar.com/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn). This signer is created and managed by Neynar, and is used to provide tofui access to your farcaster account via it's API.
+For authorization, the app directs you to create a signer via Neynar's
+[SIWN](https://docs.neynar.com/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn).
+This signer is created and managed by Neynar, and is used to provide tofui
+access to your farcaster account via it's API.
-This is done when both running locally and over SSH, and the signer is specific to whichever app credentials were used. This would be tofui over SSH, or your own app when running locally.
+This is done when both running locally and over SSH, and the signer is specific
+to whichever app credentials were used. This would be tofui over SSH, or your
+own app when running locally.
+the tofui instance (local or hosted) uses the configured Neynar app credentials
+to obtain a signer via
-the tofui instance (local or hosted) uses the configured Neynar app credentials to obtain a signer via
## Running Locally
-Running locally requires your own Neynar application. After creating one, copy [config.yaml.example](./config.yaml.example) to config.yaml and updating with your app's values.
+Running locally requires your own Neynar application. After creating one, copy
+[config.yaml.example](./config.yaml.example) to config.yaml and updating with
+your app's values.
### Install
Install using go
+
```
go install github.com/treethought/tofui@latest
```
-Or clone the repo and run
+Or clone the repo and run
+
```
make build
```
-Or download a binary from the [releases](https://github.com/treethought/tofui/releases) page
+Or download a binary from the
+[releases](https://github.com/treethought/tofui/releases) page
Then start the TUI via `tofui`
## Keybindings
+#### Navigation
+
| Key | Action |
| --------- | ------------------------------------------- |
| Tab | Toggle focus between sidebar and main panel |
@@ -59,7 +76,16 @@ Then start the TUI via `tofui`
| Enter | Select current item |
| F
| Jump to your feed |
| Ctrl-K | Open channel quick switcher
|
-| P | Open publish form |
| ? | Open help |
| c | View channel of current item |
| p | View profile of current item |
+
+#### Actions
+
+| Key | Action |
+| ------ | ---------------------------------------------- |
+| ctrl-d | Submit cast/reply in publish view |
+| P | Open publish form |
+| C | Open reply form when viewing cast |
+| o | Open current cast in browser (local mode only) |
+| l | Like current cast |
diff --git a/api/feed.go b/api/feed.go
index 96d6062..c4d1630 100644
--- a/api/feed.go
+++ b/api/feed.go
@@ -3,7 +3,6 @@ package api
import (
"context"
"fmt"
- "log"
)
type FeedRequest struct {
@@ -42,16 +41,9 @@ func (r *FeedRequest) opts() []RequestOption {
if r.Limit != 0 {
opts = append(opts, WithQuery("limit", fmt.Sprintf("%d", r.Limit)))
}
- if r.ViewerFID == 0 {
- if r.FID != 0 {
- r.ViewerFID = r.FID
- log.Println("using fid param for viewer for feed request: ", r.FID)
- } else {
- log.Println("using default viewer fid 3 for feed request")
- r.ViewerFID = 3
- }
+ if r.ViewerFID != 0 {
+ opts = append(opts, WithQuery("viewer_fid", fmt.Sprintf("%d", r.ViewerFID)))
}
- opts = append(opts, WithQuery("viewer_fid", fmt.Sprintf("%d", r.ViewerFID)))
return opts
}
diff --git a/go.sum b/go.sum
index 004ee64..6f38253 100644
--- a/go.sum
+++ b/go.sum
@@ -24,6 +24,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
+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.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkimep7FC9c40=
github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0=
github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng=
diff --git a/ui/app.go b/ui/app.go
index a9b90ff..7514d29 100644
--- a/ui/app.go
+++ b/ui/app.go
@@ -202,14 +202,9 @@ func (a *App) FocusPrev() tea.Cmd {
}
prev := a.GetModel(a.prev)
if a.prev == "" || prev == nil {
- a.SetNavName("feed")
- if f, ok := a.GetModel("feed").(*FeedView); ok {
- f.Clear()
- return tea.Sequence(f.SetDefaultParams(), a.SetFocus("feed"))
- }
-
- return a.SetFocus("feed")
+ return nil
}
+
if m := a.GetModel(a.prev); m != nil {
a.SetNavName(a.prevName)
return a.SetFocus(a.prev)
@@ -280,7 +275,9 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return a, cmd
}
feed := a.GetModel("feed").(*FeedView)
- feed.Clear()
+ if a.focused == "feed" {
+ feed.Clear()
+ }
focusCmd := a.SetFocus("feed")
a.splash.SetInfo("loading channels...")
return a, tea.Batch(feed.setItems(msg.Casts), focusCmd)
diff --git a/ui/feed.go b/ui/feed.go
index 91aec2d..28d5fea 100644
--- a/ui/feed.go
+++ b/ui/feed.go
@@ -31,7 +31,6 @@ type reactMsg struct {
type FeedView struct {
app *App
- client *api.Client
table table.Model
items []*CastFeedItem
loading *Loading
@@ -148,10 +147,14 @@ func likeCastCmd(client *api.Client, signer *api.Signer, cast *api.Cast) tea.Cmd
}
func getDefaultFeedCmd(client *api.Client, signer *api.Signer) tea.Cmd {
- req := &api.FeedRequest{FeedType: "following", Limit: 100}
+ req := &api.FeedRequest{Limit: 100}
if signer != nil {
req.FID = signer.FID
req.ViewerFID = signer.FID
+ req.FeedType = "following"
+ } else {
+ req.FeedType = "filter"
+ req.FilterType = "global_trending"
}
return getFeedCmd(client, req)
}
@@ -284,18 +287,17 @@ func (m *FeedView) ViewCurrentChannel() tea.Cmd {
if current.cast.ParentURL == "" {
return nil
}
- m.Clear()
cmds := []tea.Cmd{}
- if c, err := m.client.GetChannelByParentUrl(current.cast.ParentURL); err == nil {
+ if c, err := m.app.client.GetChannelByParentUrl(current.cast.ParentURL); err == nil {
cmds = append(cmds, navNameCmd(fmt.Sprintf("channel: %s", c.Name)))
}
cmds = append(cmds,
- focusCmd("feed"),
- getFeedCmd(m.client, &api.FeedRequest{
+ getFeedCmd(m.app.client, &api.FeedRequest{
FeedType: "filter", FilterType: "parent_url",
ParentURL: current.cast.ParentURL, Limit: 100},
),
+ focusCmd("feed"),
)
return tea.Sequence(cmds...)
diff --git a/ui/keybindings.go b/ui/keybindings.go
index 4d86a8f..aa8ba4c 100644
--- a/ui/keybindings.go
+++ b/ui/keybindings.go
@@ -142,10 +142,6 @@ func (k navKeymap) HandleMsg(a *App, msg tea.KeyMsg) tea.Cmd {
// TODO cleanup
// reset params for user's feed
var cmd tea.Cmd
- if f, ok := a.GetModel("feed").(*FeedView); ok {
- f.Clear()
- cmd = f.SetDefaultParams()
- }
a.SetNavName("feed")
a.sidebar.SetActive(false)
return tea.Sequence(cmd, a.SetFocus("feed"))
diff --git a/ui/splash.go b/ui/splash.go
index f4b3573..d9826dd 100644
--- a/ui/splash.go
+++ b/ui/splash.go
@@ -25,7 +25,6 @@ type SplashView struct {
info *viewport.Model
loading *Loading
active bool
- spinner *spinner.Model
}
func NewSplashView() *SplashView {
@@ -39,7 +38,7 @@ func NewSplashView() *SplashView {
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = NewStyle().Foreground(lipgloss.Color("205"))
- return &SplashView{vp: &vp, loading: l, info: &info, active: true, spinner: &s}
+ return &SplashView{vp: &vp, loading: l, info: &info, active: true}
}
func (m *SplashView) Active() bool {
@@ -64,27 +63,21 @@ func (m *SplashView) SetSize(w, h int) {
}
func (m *SplashView) Init() tea.Cmd {
- return tea.Batch(m.loading.Init(), m.spinner.Tick)
+ return tea.Batch(m.loading.Init())
}
func (m *SplashView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if !m.active {
return m, nil
}
- cmds := []tea.Cmd{}
- _, lcmd := m.loading.Update(msg)
- cmds = append(cmds, lcmd)
- _, scmd := m.spinner.Update(msg)
- cmds = append(cmds, scmd)
-
- cmds = append(cmds, m.spinner.Tick)
- return m, tea.Batch(cmds...)
+ _, cmd := m.loading.Update(msg)
+ return m, cmd
}
func (m *SplashView) View() string {
return splashStyle.Render(
lipgloss.JoinVertical(lipgloss.Top,
m.vp.View(),
lipgloss.NewStyle().MarginTop(1).Render(m.loading.View()),
- lipgloss.JoinHorizontal(lipgloss.Left, m.spinner.View(), " ", m.info.View()),
+ m.info.View(),
),
)
}