diff --git a/README.md b/README.md index dfd0754..b16a2fc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A TUI for Mastodon with vim inspired keys. The program misses some features but they will be added when I get time. -Press `C` to create a new toot. +Press `C` to create a new toot and `N` to focus on your notifications. You can find Linux binaries under [releases](https://github.com/RasmusLindroth/tut/releases). diff --git a/cmdbar.go b/cmdbar.go index 7b1aee2..99ea540 100644 --- a/cmdbar.go +++ b/cmdbar.go @@ -132,7 +132,7 @@ func (c *CmdBar) DoneFunc(key tcell.Key) { c.app.UI.SetFocus(LeftPaneFocus) c.app.UI.CmdBar.ClearInput() case "notifications", "n": - c.app.UI.StatusView.AddFeed(NewNoticifationsFeed(c.app)) + c.app.UI.StatusView.AddFeed(NewNotificationFeed(c.app, false)) c.app.UI.SetFocus(LeftPaneFocus) c.app.UI.CmdBar.ClearInput() } diff --git a/config.example.ini b/config.example.ini index 21d2a4a..74fbd7c 100644 --- a/config.example.ini +++ b/config.example.ini @@ -29,6 +29,11 @@ date-today-format=15:04 # default=home timeline=home +# If you want to display a list of notifications +# under your timeline feed +# default=true +notification-feed=true + [media] # Your image viewer # default=xdg-open diff --git a/config.go b/config.go index 16cfeec..fa05866 100644 --- a/config.go +++ b/config.go @@ -16,11 +16,12 @@ type Config struct { } type GeneralConfig struct { - AutoLoadNewer bool - AutoLoadSeconds int - DateTodayFormat string - DateFormat string - StartTimeline TimelineType + AutoLoadNewer bool + AutoLoadSeconds int + DateTodayFormat string + DateFormat string + StartTimeline TimelineType + NotificationFeed bool } type StyleConfig struct { @@ -178,6 +179,9 @@ func parseGeneral(cfg *ini.File) GeneralConfig { case "federated": general.StartTimeline = TimelineFederated } + + general.NotificationFeed = cfg.Section("general").Key("notification-feed").MustBool(true) + return general } @@ -288,6 +292,11 @@ date-today-format=15:04 # default=home timeline=home +# If you want to display a list of notifications +# under your timeline feed +# default=true +notification-feed=true + [media] # Your image viewer # default=xdg-open diff --git a/feed.go b/feed.go index 216c50e..0b9b82d 100644 --- a/feed.go +++ b/feed.go @@ -56,6 +56,7 @@ func showTootOptions(app *App, status *mastodon.Status, showSensitive bool) (str strippedContent, urls = cleanTootHTML(status.Content) + normal := ColorMark(app.Config.Style.Text) subtleColor := ColorMark(app.Config.Style.Subtle) special1 := ColorMark(app.Config.Style.TextSpecial1) special2 := ColorMark(app.Config.Style.TextSpecial2) @@ -98,14 +99,14 @@ func showTootOptions(app *App, status *mastodon.Status, showSensitive bool) (str output := head content := stripped if content != "" { - output += content + "\n\n" + output += normal + content + "\n\n" } var poll string if status.Poll != nil { poll += subtleColor + "Poll\n" poll += subtleColor + line - poll += fmt.Sprintf("Number of votes: %d\n\n", status.Poll.VotesCount) + poll += fmt.Sprintf(normal+"Number of votes: %d\n\n", status.Poll.VotesCount) votes := float64(status.Poll.VotesCount) for _, o := range status.Poll.Options { res := 0.0 @@ -121,7 +122,10 @@ func showTootOptions(app *App, status *mastodon.Status, showSensitive bool) (str for _, att := range status.MediaAttachments { media += subtleColor + line media += fmt.Sprintf(subtleColor+"Attached %s\n", att.Type) - media += fmt.Sprintf("%s\n", att.URL) + if att.Description != "" { + media += fmt.Sprintf(normal+"%s\n\n", tview.Escape(att.Description)) + } + media += fmt.Sprintf(normal+"%s\n", att.URL) } if len(status.MediaAttachments) > 0 { media += "\n" @@ -132,11 +136,11 @@ func showTootOptions(app *App, status *mastodon.Status, showSensitive bool) (str card += subtleColor + "Card type: " + status.Card.Type + "\n" card += subtleColor + line if status.Card.Title != "" { - card += status.Card.Title + "\n\n" + card += normal + status.Card.Title + "\n\n" } desc := strings.TrimSpace(status.Card.Description) if desc != "" { - card += desc + "\n\n" + card += normal + desc + "\n\n" } card += status.Card.URL } @@ -192,11 +196,11 @@ func showUser(app *App, user *mastodon.Account, relation *mastodon.Relationship, } text += fmt.Sprintf(s1+"%s\n\n", user.Acct) - text += fmt.Sprintf("Toots %s%d %sFollowers %s%d %sFollowing %s%d\n\n", - s2, user.StatusesCount, n, s2, user.FollowersCount, n, s2, user.FollowingCount) + text += fmt.Sprintf("%sToots %s%d %sFollowers %s%d %sFollowing %s%d\n\n", + n, s2, user.StatusesCount, n, s2, user.FollowersCount, n, s2, user.FollowingCount) note, urls := cleanTootHTML(user.Note) - text += note + "\n\n" + text += n + note + "\n\n" for _, f := range user.Fields { value, fu := cleanTootHTML(f.Value) @@ -908,9 +912,10 @@ func (u *UserFeed) Input(event *tcell.EventKey) { } } -func NewNoticifationsFeed(app *App) *NotificationsFeed { +func NewNotificationFeed(app *App, docked bool) *NotificationsFeed { n := &NotificationsFeed{ - app: app, + app: app, + docked: docked, } n.notifications, _ = n.app.API.GetNotifications() return n @@ -920,6 +925,7 @@ type NotificationsFeed struct { app *App timelineType TimelineType notifications []*mastodon.Notification + docked bool index int showSpoiler bool } @@ -933,7 +939,12 @@ func (n *NotificationsFeed) GetDesc() string { } func (n *NotificationsFeed) GetCurrentNotification() *mastodon.Notification { - index := n.app.UI.StatusView.GetCurrentItem() + var index int + if n.docked { + index = n.app.UI.StatusView.notificationView.list.GetCurrentItem() + } else { + index = n.app.UI.StatusView.GetCurrentItem() + } if index >= len(n.notifications) { return nil } @@ -1017,7 +1028,11 @@ func (n *NotificationsFeed) LoadOlder() int { } func (n *NotificationsFeed) DrawList() { - n.app.UI.StatusView.SetList(n.GetFeedList()) + if n.docked { + n.app.UI.StatusView.notificationView.SetList(n.GetFeedList()) + } else { + n.app.UI.StatusView.SetList(n.GetFeedList()) + } } func (n *NotificationsFeed) DrawSpoiler() { @@ -1026,7 +1041,11 @@ func (n *NotificationsFeed) DrawSpoiler() { } func (n *NotificationsFeed) DrawToot() { - n.index = n.app.UI.StatusView.GetCurrentItem() + if n.docked { + n.index = n.app.UI.StatusView.notificationView.list.GetCurrentItem() + } else { + n.index = n.app.UI.StatusView.GetCurrentItem() + } notification := n.GetCurrentNotification() if notification == nil { n.app.UI.StatusView.SetText("") diff --git a/go.mod b/go.mod index cb27c34..61c897c 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.14 require ( github.com/gdamore/tcell v1.3.0 github.com/kyoh86/xdg v1.2.0 - github.com/mattn/go-mastodon v0.0.5-0.20200302023913-3e91c76504df - github.com/microcosm-cc/bluemonday v1.0.2 - github.com/pelletier/go-toml v1.7.0 - github.com/rivo/tview v0.0.0-20200404204604-ca37f83cb2e7 + github.com/mattn/go-mastodon v0.0.5-0.20200727014106-315df7d9162e + github.com/microcosm-cc/bluemonday v1.0.3 + github.com/pelletier/go-toml v1.8.0 + github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92 github.com/smartystreets/goconvey v1.6.4 // indirect - golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e - gopkg.in/ini.v1 v1.55.0 + golang.org/x/net v0.0.0-20200707034311-ab3426394381 + gopkg.in/ini.v1 v1.57.0 ) diff --git a/go.sum b/go.sum index 3b13f6b..0c843be 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,10 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +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/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU= +github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE= 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/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -12,6 +16,8 @@ github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +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/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -25,8 +31,8 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-mastodon v0.0.5-0.20200302023913-3e91c76504df h1:LC2XTWQfaJCZrSfaCHoy3GxbHeWrqZPHLxiapTh+dZk= -github.com/mattn/go-mastodon v0.0.5-0.20200302023913-3e91c76504df/go.mod h1:CZ3bPYRNGgvMZr4d/SNMvCObyCQvTgCXdIOrW040z5U= +github.com/mattn/go-mastodon v0.0.5-0.20200727014106-315df7d9162e h1:j6N81C3ganbpJ5p5xCPO+7QQHw97o/kdOB2ma3+sSSg= +github.com/mattn/go-mastodon v0.0.5-0.20200727014106-315df7d9162e/go.mod h1:CZ3bPYRNGgvMZr4d/SNMvCObyCQvTgCXdIOrW040z5U= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= @@ -34,10 +40,16 @@ github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/microcosm-cc/bluemonday v1.0.3 h1:EjVH7OqbU219kdm8acbveoclh2zZFqPJTJw6VUlTLAQ= +github.com/microcosm-cc/bluemonday v1.0.3/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/rivo/tview v0.0.0-20200404204604-ca37f83cb2e7 h1:Jfm2O5tRzzHt5LeM9F4AuwcNGxCH7erPl8GeVOzJKd0= github.com/rivo/tview v0.0.0-20200404204604-ca37f83cb2e7/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= +github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92 h1:rqaqSUdaW+OBbjnsrOoiaJv43mSRARuvsAuirmdxu7E= +github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -48,16 +60,21 @@ github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJ github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -75,5 +92,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ= gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 8df51bb..956242f 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,7 @@ import ( "github.com/gdamore/tcell" ) -const version string = "0.0.14" +const version string = "0.0.15" func main() { @@ -195,6 +195,8 @@ func main() { app.UI.MediaOverlay.Prev() case 'd', 'D': app.UI.MediaOverlay.Delete() + case 'e', 'E': + app.UI.MediaOverlay.EditDesc() case 'a', 'A': app.UI.MediaOverlay.SetFocus(MediaFocusAdd) case 'q', 'Q': @@ -236,7 +238,7 @@ func main() { return event } - if app.UI.Focus == LeftPaneFocus || app.UI.Focus == RightPaneFocus { + if app.UI.Focus == LeftPaneFocus || app.UI.Focus == RightPaneFocus || app.UI.Focus == NotificationPaneFocus { if event.Key() == tcell.KeyRune { switch event.Rune() { case ':': diff --git a/media.go b/media.go index aa934c0..670c630 100644 --- a/media.go +++ b/media.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os" "path/filepath" "strings" @@ -35,7 +36,7 @@ func NewMediaOverlay(app *App) *MediaView { m.FileList.SetHighlightFullLine(true) m.TextTop.SetBackgroundColor(app.Config.Style.Background) - m.TextTop.SetTextColor(app.Config.Style.Text) + m.TextTop.SetTextColor(app.Config.Style.Subtle) m.TextBottom.SetBackgroundColor(app.Config.Style.Background) m.TextBottom.SetTextColor(app.Config.Style.Text) @@ -59,7 +60,12 @@ type MediaView struct { FileList *tview.List InputField *MediaInput Focus MediaFocus - Files []string + Files []UploadFile +} + +type UploadFile struct { + Path string + Description string } func (m *MediaView) Reset() { @@ -70,16 +76,26 @@ func (m *MediaView) Reset() { } func (m *MediaView) AddFile(f string) { - m.Files = append(m.Files, f) + file := UploadFile{Path: f} + m.Files = append(m.Files, file) m.FileList.AddItem(filepath.Base(f), "", 0, nil) m.Draw() } func (m *MediaView) Draw() { - m.TextTop.SetText("List of attached files:") + topText := "Current file description: " + + index := m.FileList.GetCurrentItem() + if len(m.Files) != 0 && index < len(m.Files) && m.Files[index].Description != "" { + topText += tview.Escape(m.Files[index].Description) + } + + m.TextTop.SetText(topText) + var items []string items = append(items, ColorKey(m.app.Config.Style, "", "A", "dd file")) items = append(items, ColorKey(m.app.Config.Style, "", "D", "elete file")) + items = append(items, ColorKey(m.app.Config.Style, "", "E", "dit desc")) items = append(items, ColorKey(m.app.Config.Style, "", "Esc", " Done")) m.TextBottom.SetText(strings.Join(items, " ")) } @@ -110,6 +126,7 @@ func (m *MediaView) Prev() { if index-1 >= 0 { m.FileList.SetCurrentItem(index - 1) } + m.Draw() } func (m *MediaView) Next() { @@ -117,6 +134,7 @@ func (m *MediaView) Next() { if index+1 < m.FileList.GetItemCount() { m.FileList.SetCurrentItem(index + 1) } + m.Draw() } func (m *MediaView) Delete() { @@ -126,6 +144,24 @@ func (m *MediaView) Delete() { } m.FileList.RemoveItem(index) m.Files = append(m.Files[:index], m.Files[index+1:]...) + m.Draw() +} + +func (m *MediaView) EditDesc() { + index := m.FileList.GetCurrentItem() + if len(m.Files) == 0 || index > len(m.Files) { + return + } + file := m.Files[index] + desc, err := openEditor(m.app.UI.Root, file.Description) + if err != nil { + m.app.UI.CmdBar.ShowError(fmt.Sprintf("Couldn't edit description. Error: %v\n", err)) + m.Draw() + return + } + file.Description = desc + m.Files[index] = file + m.Draw() } type MediaInput struct { diff --git a/messagebox.go b/messagebox.go index 7b9e04f..26627b5 100644 --- a/messagebox.go +++ b/messagebox.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "os" "strings" "time" @@ -125,7 +126,18 @@ func (m *MessageBox) Post() { attachments := m.app.UI.MediaOverlay.Files for _, ap := range attachments { - a, err := m.app.API.Client.UploadMedia(context.Background(), ap) + f, err := os.Open(ap.Path) + if err != nil { + m.app.UI.CmdBar.ShowError(fmt.Sprintf("Couldn't upload media. Error: %v\n", err)) + return + } + media := &mastodon.Media{ + File: f, + } + if ap.Description != "" { + media.Description = ap.Description + } + a, err := m.app.API.Client.UploadMediaFromMedia(context.Background(), media) if err != nil { m.app.UI.CmdBar.ShowError(fmt.Sprintf("Couldn't upload media. Error: %v\n", err)) return diff --git a/notifications.go b/notifications.go new file mode 100644 index 0000000..26598c4 --- /dev/null +++ b/notifications.go @@ -0,0 +1,80 @@ +package main + +import "github.com/rivo/tview" + +type NotificationView struct { + app *App + list *tview.List + feed Feed + loadingNewer bool + loadingOlder bool +} + +func NewNotificationView(app *App) *NotificationView { + nv := &NotificationView{ + app: app, + loadingNewer: false, + loadingOlder: false, + } + + nv.list = tview.NewList() + nv.list.SetMainTextColor(app.Config.Style.Text) + nv.list.SetBackgroundColor(app.Config.Style.Background) + nv.list.SetSelectedTextColor(app.Config.Style.StatusBarViewText) + nv.list.SetSelectedBackgroundColor(app.Config.Style.StatusBarViewBackground) + nv.list.ShowSecondaryText(false) + nv.list.SetHighlightFullLine(true) + + nv.feed = NewNotificationFeed(app, true) + + return nv +} + +func (n *NotificationView) SetList(items <-chan string) { + n.list.Clear() + for s := range items { + n.list.AddItem(s, "", 0, nil) + } +} + +func (n *NotificationView) loadNewer() { + if n.loadingNewer { + return + } + n.loadingNewer = true + go func() { + new := n.feed.LoadNewer() + if new == 0 { + n.loadingNewer = false + return + } + n.app.UI.Root.QueueUpdateDraw(func() { + index := n.list.GetCurrentItem() + n.feed.DrawList() + newIndex := index + new + + n.list.SetCurrentItem(newIndex) + n.loadingNewer = false + }) + }() +} + +func (n *NotificationView) loadOlder() { + if n.loadingOlder { + return + } + n.loadingOlder = true + go func() { + new := n.feed.LoadOlder() + if new == 0 { + n.loadingOlder = false + return + } + n.app.UI.Root.QueueUpdateDraw(func() { + index := n.list.GetCurrentItem() + n.feed.DrawList() + n.list.SetCurrentItem(index) + n.loadingOlder = false + }) + }() +} diff --git a/statusview.go b/statusview.go index 2177999..4822bb2 100644 --- a/statusview.go +++ b/statusview.go @@ -16,9 +16,14 @@ func NewStatusView(app *App, tl TimelineType) *StatusView { text: tview.NewTextView(), controls: tview.NewTextView(), focus: LeftPaneFocus, + lastList: LeftPaneFocus, loadingNewer: false, loadingOlder: false, } + if t.app.Config.General.NotificationFeed { + t.notificationView = NewNotificationView(app) + } + t.flex = tview.NewFlex().SetDirection(tview.FlexRow). AddItem(t.text, 0, 9, false). AddItem(t.controls, 1, 0, false) @@ -47,20 +52,34 @@ func NewStatusView(app *App, tl TimelineType) *StatusView { } } }() + if app.Config.General.NotificationFeed { + go func() { + d := time.Second * time.Duration(app.Config.General.AutoLoadSeconds) + ticker := time.NewTicker(d) + for { + select { + case <-ticker.C: + t.notificationView.loadNewer() + } + } + }() + } } return t } type StatusView struct { - app *App - list *tview.List - flex *tview.Flex - text *tview.TextView - controls *tview.TextView - feeds []Feed - focus FocusAt - loadingNewer bool - loadingOlder bool + app *App + list *tview.List + flex *tview.Flex + text *tview.TextView + controls *tview.TextView + feeds []Feed + focus FocusAt + lastList FocusAt + loadingNewer bool + loadingOlder bool + notificationView *NotificationView } func (t *StatusView) AddFeed(f Feed) { @@ -89,6 +108,14 @@ func (t *StatusView) GetLeftView() tview.Primitive { return t.list } +func (t *StatusView) GetNotificationView() tview.Primitive { + if t.notificationView != nil { + t.notificationView.feed.DrawList() + return t.notificationView.list + } + return nil +} + func (t *StatusView) GetRightView() tview.Primitive { return t.flex } @@ -127,12 +154,6 @@ func (t *StatusView) inputBoth(event *tcell.EventKey) { t.home() case 'G': t.end() - case 'q', 'Q': - if len(t.feeds) > 1 { - t.RemoveLatestFeed() - } else { - t.app.UI.Root.Stop() - } } } else { switch event.Key() { @@ -150,6 +171,25 @@ func (t *StatusView) inputBoth(event *tcell.EventKey) { } } +func (t *StatusView) inputBack(q bool) { + if t.app.UI.Focus == LeftPaneFocus && len(t.feeds) > 1 { + t.RemoveLatestFeed() + } else if t.app.UI.Focus == LeftPaneFocus && q { + t.app.UI.Root.Stop() + } else if t.app.UI.Focus == NotificationPaneFocus { + t.app.UI.SetFocus(LeftPaneFocus) + t.focus = LeftPaneFocus + t.lastList = LeftPaneFocus + t.app.UI.StatusBar.Text.SetBackgroundColor( + t.app.Config.Style.StatusBarBackground, + ) + t.app.UI.StatusBar.Text.SetTextColor( + t.app.Config.Style.StatusBarText, + ) + t.feeds[len(t.feeds)-1].DrawToot() + } +} + func (t *StatusView) inputLeft(event *tcell.EventKey) { if event.Key() == tcell.KeyRune { switch event.Rune() { @@ -166,6 +206,15 @@ func (t *StatusView) inputLeft(event *tcell.EventKey) { t.prev() case 'j', 'J': t.next() + case 'n', 'N': + if t.app.Config.General.NotificationFeed { + t.app.UI.SetFocus(NotificationPaneFocus) + t.focus = NotificationPaneFocus + t.lastList = NotificationPaneFocus + t.notificationView.feed.DrawToot() + } + case 'q', 'Q': + t.inputBack(true) } } else { switch event.Key() { @@ -178,29 +227,37 @@ func (t *StatusView) inputLeft(event *tcell.EventKey) { case tcell.KeyPgDn, tcell.KeyCtrlF: t.pgdown() case tcell.KeyEsc: - if len(t.feeds) > 1 { - t.RemoveLatestFeed() - } + t.inputBack(false) } } } +func (t *StatusView) inputRightQuit() { + if t.lastList == LeftPaneFocus { + t.app.UI.SetFocus(LeftPaneFocus) + t.focus = LeftPaneFocus + } else if t.lastList == NotificationPaneFocus { + t.app.UI.SetFocus(NotificationPaneFocus) + t.focus = NotificationPaneFocus + } + t.app.UI.StatusBar.Text.SetBackgroundColor( + t.app.Config.Style.StatusBarBackground, + ) + t.app.UI.StatusBar.Text.SetTextColor( + t.app.Config.Style.StatusBarText, + ) +} + func (t *StatusView) inputRight(event *tcell.EventKey) { if event.Key() == tcell.KeyRune { switch event.Rune() { - + case 'q', 'Q': + t.inputRightQuit() } } else { switch event.Key() { case tcell.KeyEsc: - t.app.UI.SetFocus(LeftPaneFocus) - t.focus = LeftPaneFocus - t.app.UI.StatusBar.Text.SetBackgroundColor( - t.app.Config.Style.StatusBarBackground, - ) - t.app.UI.StatusBar.Text.SetTextColor( - t.app.Config.Style.StatusBarText, - ) + t.inputRightQuit() } } } @@ -211,10 +268,14 @@ func (t *StatusView) Input(event *tcell.EventKey) *tcell.EventKey { return event } - if t.focus == LeftPaneFocus { + switch t.focus { + case LeftPaneFocus: t.inputLeft(event) return nil - } else { + case NotificationPaneFocus: + t.inputLeft(event) + return nil + default: t.inputRight(event) } @@ -248,69 +309,163 @@ func (t *StatusView) drawDesc() { } func (t *StatusView) prev() { - current := t.list.GetCurrentItem() + var current int + var list *tview.List + var feed Feed + if t.app.UI.Focus == LeftPaneFocus { + current = t.GetCurrentItem() + list = t.list + feed = t.feeds[len(t.feeds)-1] + } else { + current = t.notificationView.list.GetCurrentItem() + list = t.notificationView.list + feed = t.notificationView.feed + } + if current-1 >= 0 { current-- } - t.list.SetCurrentItem(current) - t.feeds[len(t.feeds)-1].DrawToot() + list.SetCurrentItem(current) + feed.DrawToot() if current < 4 { - t.loadNewer() + switch t.app.UI.Focus { + case LeftPaneFocus: + t.loadNewer() + case NotificationPaneFocus: + t.notificationView.loadNewer() + } } } func (t *StatusView) next() { - t.list.SetCurrentItem( - t.list.GetCurrentItem() + 1, + var list *tview.List + var feed Feed + if t.app.UI.Focus == LeftPaneFocus { + list = t.list + feed = t.feeds[len(t.feeds)-1] + } else { + list = t.notificationView.list + feed = t.notificationView.feed + } + + list.SetCurrentItem( + list.GetCurrentItem() + 1, ) - t.feeds[len(t.feeds)-1].DrawToot() + feed.DrawToot() - count := t.list.GetItemCount() - current := t.list.GetCurrentItem() + count := list.GetItemCount() + current := list.GetCurrentItem() if (count - current + 1) < 5 { - t.loadOlder() + switch t.app.UI.Focus { + case LeftPaneFocus: + t.loadOlder() + case NotificationPaneFocus: + t.notificationView.loadOlder() + } } } func (t *StatusView) pgdown() { - _, _, _, height := t.list.GetInnerRect() - i := t.GetCurrentItem() + height - 1 - t.list.SetCurrentItem(i) - t.feeds[len(t.feeds)-1].DrawToot() + var list *tview.List + var feed Feed + if t.app.UI.Focus == LeftPaneFocus { + list = t.list + feed = t.feeds[len(t.feeds)-1] + } else { + list = t.notificationView.list + feed = t.notificationView.feed + } - count := t.list.GetItemCount() - current := t.list.GetCurrentItem() + _, _, _, height := list.GetInnerRect() + i := list.GetCurrentItem() + height - 1 + list.SetCurrentItem(i) + feed.DrawToot() + + count := list.GetItemCount() + current := list.GetCurrentItem() if (count - current + 1) < 5 { - t.loadOlder() + switch t.app.UI.Focus { + case LeftPaneFocus: + t.loadOlder() + case NotificationPaneFocus: + t.notificationView.loadOlder() + } } } func (t *StatusView) pgup() { - _, _, _, height := t.list.GetInnerRect() - i := t.GetCurrentItem() - height + 1 + var list *tview.List + var feed Feed + if t.app.UI.Focus == LeftPaneFocus { + list = t.list + feed = t.feeds[len(t.feeds)-1] + } else { + list = t.notificationView.list + feed = t.notificationView.feed + } + + _, _, _, height := list.GetInnerRect() + i := list.GetCurrentItem() - height + 1 if i < 0 { i = 0 } - t.list.SetCurrentItem(i) - t.feeds[len(t.feeds)-1].DrawToot() + list.SetCurrentItem(i) + feed.DrawToot() - current := t.list.GetCurrentItem() + current := list.GetCurrentItem() if current < 4 { - t.loadNewer() + switch t.app.UI.Focus { + case LeftPaneFocus: + t.loadNewer() + case NotificationPaneFocus: + t.notificationView.loadNewer() + } } } func (t *StatusView) home() { - t.list.SetCurrentItem(0) - t.feeds[len(t.feeds)-1].DrawToot() - t.loadOlder() + var list *tview.List + var feed Feed + if t.app.UI.Focus == LeftPaneFocus { + list = t.list + feed = t.feeds[len(t.feeds)-1] + } else { + list = t.notificationView.list + feed = t.notificationView.feed + } + + list.SetCurrentItem(0) + feed.DrawToot() + + switch t.app.UI.Focus { + case LeftPaneFocus: + t.loadNewer() + case NotificationPaneFocus: + t.notificationView.loadNewer() + } } func (t *StatusView) end() { - t.list.SetCurrentItem(-1) - t.feeds[len(t.feeds)-1].DrawToot() - t.loadOlder() + var list *tview.List + var feed Feed + if t.app.UI.Focus == LeftPaneFocus { + list = t.list + feed = t.feeds[len(t.feeds)-1] + } else { + list = t.notificationView.list + feed = t.notificationView.feed + } + + list.SetCurrentItem(-1) + feed.DrawToot() + + switch t.app.UI.Focus { + case LeftPaneFocus: + t.loadOlder() + case NotificationPaneFocus: + t.notificationView.loadOlder() + } } func (t *StatusView) loadNewer() { diff --git a/ui.go b/ui.go index ae2acec..4fd7929 100644 --- a/ui.go +++ b/ui.go @@ -15,6 +15,7 @@ type FocusAt uint const ( LeftPaneFocus FocusAt = iota RightPaneFocus + NotificationPaneFocus CmdBarFocus MessageFocus MessageAttachmentFocus @@ -125,7 +126,7 @@ func (ui *UI) Init() { AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(nil, 0, 1, false). AddItem(ui.MediaOverlay.Flex.SetDirection(tview.FlexRow). - AddItem(ui.MediaOverlay.TextTop, 1, 1, true). + AddItem(ui.MediaOverlay.TextTop, 2, 1, true). AddItem(ui.MediaOverlay.FileList, 0, 10, true). AddItem(ui.MediaOverlay.TextBottom, 1, 1, true). AddItem(ui.MediaOverlay.InputField.View, 2, 1, false), 0, 8, false). @@ -194,7 +195,39 @@ func (ui *UI) SetFocus(f FocusAt) { case AuthOverlayFocus: ui.Pages.ShowPage("login") ui.FocusAt(ui.AuthOverlay.Input, "-- LOGIN --") + case NotificationPaneFocus: + ui.Pages.SwitchToPage("main") + ui.FocusAt(nil, "-- NOTIFICATIONS --") + + ui.StatusView.notificationView.list.SetSelectedBackgroundColor( + ui.app.Config.Style.ListSelectedBackground, + ) + ui.StatusView.notificationView.list.SetSelectedTextColor( + ui.app.Config.Style.ListSelectedText, + ) + + ui.StatusView.list.SetSelectedBackgroundColor( + ui.app.Config.Style.StatusBarViewBackground, + ) + ui.StatusView.list.SetSelectedTextColor( + ui.app.Config.Style.StatusBarViewText, + ) default: + ui.StatusView.list.SetSelectedBackgroundColor( + ui.app.Config.Style.ListSelectedBackground, + ) + ui.StatusView.list.SetSelectedTextColor( + ui.app.Config.Style.ListSelectedText, + ) + + if ui.app.Config.General.NotificationFeed { + ui.StatusView.notificationView.list.SetSelectedBackgroundColor( + ui.app.Config.Style.StatusBarViewBackground, + ) + ui.StatusView.notificationView.list.SetSelectedTextColor( + ui.app.Config.Style.StatusBarViewText, + ) + } ui.Pages.SwitchToPage("main") ui.FocusAt(nil, "-- LIST --") } @@ -271,20 +304,46 @@ func (ui *UI) LoggedIn() { } return 0, 0, 0, 0 }) + ui.Pages.RemovePage("main") - ui.Pages.AddPage("main", - tview.NewFlex(). - AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(ui.Top.Text, 1, 0, false). - AddItem(tview.NewFlex().SetDirection(tview.FlexColumn). - AddItem(ui.StatusView.GetLeftView(), 0, 2, false). - AddItem(verticalLine, 1, 0, false). - AddItem(tview.NewBox().SetBackgroundColor(ui.app.Config.Style.Background), 1, 0, false). - AddItem(ui.StatusView.GetRightView(), - 0, 4, false), - 0, 1, false). - AddItem(ui.StatusBar.Text, 1, 1, false). - AddItem(ui.CmdBar.Input, 1, 0, false), 0, 1, false), true, true) + if ui.app.Config.General.NotificationFeed { + notificationText := tview.NewTextView() + notificationText.SetBackgroundColor(ui.app.Config.Style.Background) + notificationText.SetTextColor(ui.app.Config.Style.Subtle) + notificationText.SetText("[N]otifications") + notificationText.SetTextAlign(tview.AlignCenter) + + ui.Pages.AddPage("main", + tview.NewFlex(). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(ui.Top.Text, 1, 0, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexColumn). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(ui.StatusView.GetLeftView(), 0, 2, false). + AddItem(notificationText, 1, 0, false). + AddItem(ui.StatusView.GetNotificationView(), 0, 1, false), 0, 2, false). + AddItem(verticalLine, 1, 0, false). + AddItem(tview.NewBox().SetBackgroundColor(ui.app.Config.Style.Background), 1, 0, false). + AddItem(ui.StatusView.GetRightView(), + 0, 4, false), + 0, 1, false). + AddItem(ui.StatusBar.Text, 1, 1, false). + AddItem(ui.CmdBar.Input, 1, 0, false), 0, 1, false), true, true) + } else { + ui.Pages.AddPage("main", + tview.NewFlex(). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(ui.Top.Text, 1, 0, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexColumn). + AddItem(ui.StatusView.GetLeftView(), 0, 2, false). + AddItem(verticalLine, 1, 0, false). + AddItem(tview.NewBox().SetBackgroundColor(ui.app.Config.Style.Background), 1, 0, false). + AddItem(ui.StatusView.GetRightView(), + 0, 4, false), + 0, 1, false). + AddItem(ui.StatusBar.Text, 1, 1, false). + AddItem(ui.CmdBar.Input, 1, 0, false), 0, 1, false), true, true) + } ui.Pages.SendToBack("main") ui.SetFocus(LeftPaneFocus) diff --git a/util.go b/util.go index 1cbd0b7..5ac9d38 100644 --- a/util.go +++ b/util.go @@ -252,7 +252,7 @@ func ColorKey(style StyleConfig, pre, key, end string) string { color := ColorMark(style.TextSpecial2) normal := ColorMark(style.Text) key = tview.Escape("[" + key + "]") - text := fmt.Sprintf("%s%s%s%s%s", pre, color, key, normal, end) + text := fmt.Sprintf("%s%s%s%s%s%s", normal, pre, color, key, normal, end) return text }