Browse Source

Merge pull request #17 from RasmusLindroth/release-0.0.15

Release 0.0.15
pull/21/head 0.0.15
Rasmus Lindroth 6 years ago committed by GitHub
parent
commit
1ef5475a36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 2
      cmdbar.go
  3. 5
      config.example.ini
  4. 19
      config.go
  5. 45
      feed.go
  6. 12
      go.mod
  7. 24
      go.sum
  8. 6
      main.go
  9. 44
      media.go
  10. 14
      messagebox.go
  11. 80
      notifications.go
  12. 271
      statusview.go
  13. 87
      ui.go
  14. 2
      util.go

2
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).

2
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()
}

5
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

19
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

45
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("")

12
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
)

24
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=

6
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 ':':

44
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 {

14
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

80
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
})
}()
}

271
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() {

87
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)

2
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
}

Loading…
Cancel
Save