Browse Source

1.0.26 (#221)

* bump version

* change style of media input

* notifications with info

* add stick-to-top command

* delete temp files after viewing them (#199)

* hide notifications

Co-authored-by: Omar Polo <op@omarpolo.com>
pull/224/head 1.0.26
Rasmus Lindroth 3 years ago committed by GitHub
parent
commit
984aca6362
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      README.md
  2. 9
      api/feed.go
  3. 4
      api/stream.go
  4. 10
      config.example.ini
  5. 100
      config/config.go
  6. 10
      config/default_config.go
  7. 3
      config/help.tmpl
  8. 178
      feed/feed.go
  9. 2
      go.mod
  10. 14
      go.sum
  11. 2
      main.go
  12. 5
      ui/cmdbar.go
  13. 4
      ui/commands.go
  14. 12
      ui/feed.go
  15. 2
      ui/input.go
  16. 2
      ui/item.go
  17. 2
      ui/item_user.go
  18. 38
      ui/media.go
  19. 1
      ui/styled_elements.go
  20. 10
      ui/tutview.go

1
README.md

@ -65,6 +65,7 @@ You can find Linux binaries under [releases](https://github.com/RasmusLindroth/t
* `:proportions` [int] [int], where the first integer is the list and the other content, e.g. `:proportions 1 3`
* `:requests` see following requests
* `:saved` alias for bookmarks
* `:stick-to-top` toggle the stick-to-top setting.
* `:tag` followed by the hashtag e.g. `:tag linux`
* `:tags` list of followed tags
* `:unfollow-tag` followed by the hashtag to unfollow e.g. `:unfollow-tag tut`

9
api/feed.go

@ -5,6 +5,7 @@ import (
"strings"
"github.com/RasmusLindroth/go-mastodon"
"github.com/RasmusLindroth/tut/config"
)
type TimelineType uint
@ -72,9 +73,13 @@ func (ac *AccountClient) GetTimelineLocal(pg *mastodon.Pagination) ([]Item, erro
return ac.getStatusSimilar(fn, "public")
}
func (ac *AccountClient) GetNotifications(pg *mastodon.Pagination) ([]Item, error) {
func (ac *AccountClient) GetNotifications(nth []config.NotificationToHide, pg *mastodon.Pagination) ([]Item, error) {
var items []Item
notifications, err := ac.Client.GetNotifications(context.Background(), pg)
toHide := []string{}
for _, n := range nth {
toHide = append(toHide, string(n))
}
notifications, err := ac.Client.GetNotificationsExclude(context.Background(), &toHide, pg)
if err != nil {
return items, err
}

4
api/stream.go

@ -83,14 +83,14 @@ func (s *Stream) listen() {
switch e.(type) {
case *mastodon.UpdateEvent, *mastodon.NotificationEvent, *mastodon.DeleteEvent, *mastodon.ErrorEvent:
for _, r := range s.receivers {
go func(rec *Receiver) {
go func(rec *Receiver, e mastodon.Event) {
rec.mux.Lock()
if rec.Closed {
return
}
rec.mux.Unlock()
rec.Ch <- e
}(r)
}(r, e)
}
}
}

10
config.example.ini

@ -98,6 +98,12 @@ list-proportion=1
# default=2
content-proportion=2
# Hide notifications of this type. If you have multiple you separate them with a
# comma. Valid types: mention, status, boost, follow, follow_request, favorite,
# poll, edit.
# default=
notifications-to-hide=
# If you always want to quote original message when replying.
# default=false
quote-reply=false
@ -161,8 +167,8 @@ leader-timeout=1000
# Available commands: home, direct, local, federated, clear-notifications,
# compose, edit, history, blocking, bookmarks, saved, favorited, boosts,
# favorites, following, followers, muting, newer, preferences, profile,
# notifications, lists, tag, tags, window, list-placement, list-split,
# proportions
# notifications, lists, stick-to-top, tag, tags, window, list-placement,
# list-split, proportions
#
# The shortcuts are up to you, but keep them quite short and make sure they
# don't collide. If you have one shortcut that is "f" and an other one that is

100
config/config.go

@ -76,6 +76,7 @@ const (
LeaderLists
LeaderTag
LeaderTags
LeaderStickToTop
LeaderHistory
LeaderUser
LeaderWindow
@ -112,6 +113,19 @@ const (
ListUsersAdd
)
type NotificationToHide string
const (
HideMention NotificationToHide = "mention"
HideStatus NotificationToHide = "status"
HideBoost NotificationToHide = "reblog"
HideFollow NotificationToHide = "follow"
HideFollowRequest NotificationToHide = "follow_request"
HideFavorite NotificationToHide = "favourite"
HidePoll NotificationToHide = "poll"
HideEdited NotificationToHide = "update"
)
type Timeline struct {
FeedType FeedType
Subaction string
@ -122,32 +136,33 @@ type Timeline struct {
}
type General struct {
Confirmation bool
MouseSupport bool
DateTodayFormat string
DateFormat string
DateRelative int
MaxWidth int
StartTimeline FeedType
NotificationFeed bool
QuoteReply bool
CharLimit int
ShortHints bool
ShowFilterPhrase bool
ListPlacement ListPlacement
ListSplit ListSplit
ListProportion int
ContentProportion int
TerminalTitle int
ShowIcons bool
ShowHelp bool
RedrawUI bool
LeaderKey rune
LeaderTimeout int64
LeaderActions []LeaderAction
TimelineName bool
Timelines []Timeline
StickToTop bool
Confirmation bool
MouseSupport bool
DateTodayFormat string
DateFormat string
DateRelative int
MaxWidth int
StartTimeline FeedType
NotificationFeed bool
QuoteReply bool
CharLimit int
ShortHints bool
ShowFilterPhrase bool
ListPlacement ListPlacement
ListSplit ListSplit
ListProportion int
ContentProportion int
TerminalTitle int
ShowIcons bool
ShowHelp bool
RedrawUI bool
LeaderKey rune
LeaderTimeout int64
LeaderActions []LeaderAction
TimelineName bool
Timelines []Timeline
StickToTop bool
NotificationsToHide []NotificationToHide
}
type Style struct {
@ -945,6 +960,8 @@ func parseGeneral(cfg *ini.File) General {
la.Command = LeaderNotifications
case "lists":
la.Command = LeaderLists
case "stick-to-top":
la.Command = LeaderStickToTop
case "tag":
la.Command = LeaderTag
la.Subaction = subaction
@ -1080,6 +1097,37 @@ func parseGeneral(cfg *ini.File) General {
general.TerminalTitle = 0
}
nths := []NotificationToHide{}
nth := cfg.Section("general").Key("notifications-to-hide").MustString("")
parts := strings.Split(nth, ",")
for _, p := range parts {
s := strings.TrimSpace(p)
switch s {
case "mention":
nths = append(nths, HideMention)
case "status":
nths = append(nths, HideStatus)
case "boost":
nths = append(nths, HideBoost)
case "follow":
nths = append(nths, HideFollow)
case "follow_request":
nths = append(nths, HideFollowRequest)
case "favorite":
nths = append(nths, HideFavorite)
case "poll":
nths = append(nths, HidePoll)
case "edit":
nths = append(nths, HideEdited)
default:
if len(s) > 0 {
log.Fatalf("%s in notifications-to-hide is invalid\n", s)
os.Exit(1)
}
}
general.NotificationsToHide = nths
}
return general
}

10
config/default_config.go

@ -100,6 +100,12 @@ list-proportion=1
# default=2
content-proportion=2
# Hide notifications of this type. If you have multiple you separate them with a
# comma. Valid types: mention, status, boost, follow, follow_request, favorite,
# poll, edit.
# default=
notifications-to-hide=
# If you always want to quote original message when replying.
# default=false
quote-reply=false
@ -163,8 +169,8 @@ leader-timeout=1000
# Available commands: home, direct, local, federated, clear-notifications,
# compose, edit, history, blocking, bookmarks, saved, favorited, boosts,
# favorites, following, followers, muting, newer, preferences, profile,
# notifications, lists, tag, tags, window, list-placement, list-split,
# proportions
# notifications, lists, stick-to-top, tag, tags, window, list-placement,
# list-split, proportions
#
# The shortcuts are up to you, but keep them quite short and make sure they
# don't collide. If you have one shortcut that is "f" and an other one that is

3
config/help.tmpl

@ -100,6 +100,9 @@ Here's a list of supported commands.
{{ Color .Style.TextSpecial2 }}{{ Flags "b" }}:requests{{ Flags "-" }}{{ Color .Style.Text }}
See following requests
{{ Color .Style.TextSpecial2 }}{{ Flags "b" }}:stick-to-top{{ Flags "-" }}{{ Color .Style.Text }}
Toggle the stick-to-top setting.
{{ Color .Style.TextSpecial2 }}{{ Flags "b" }}:saved{{ Flags "-" }}{{ Color .Style.Text }}
Alias for :bookmarks

178
feed/feed.go

@ -10,9 +10,11 @@ import (
"github.com/RasmusLindroth/go-mastodon"
"github.com/RasmusLindroth/tut/api"
"github.com/RasmusLindroth/tut/config"
"golang.org/x/exp/slices"
)
type apiFunc func(pg *mastodon.Pagination) ([]api.Item, error)
type apiFuncNotification func(nth []config.NotificationToHide, pg *mastodon.Pagination) ([]api.Item, error)
type apiEmptyFunc func() ([]api.Item, error)
type apiIDFunc func(pg *mastodon.Pagination, id mastodon.ID) ([]api.Item, error)
type apiIDFuncData func(pg *mastodon.Pagination, id mastodon.ID, data interface{}) ([]api.Item, error)
@ -29,7 +31,7 @@ type LoadingLock struct {
type DesktopNotificationType uint
const (
DeskstopNotificationNone DesktopNotificationType = iota
DesktopNotificationNone DesktopNotificationType = iota
DesktopNotificationFollower
DesktopNotificationFavorite
DesktopNotificationMention
@ -39,6 +41,11 @@ const (
DesktopNotificationPost
)
type DesktopNotificationHolder struct {
Type DesktopNotificationType
Data string
}
type Feed struct {
accountClient *api.AccountClient
config *config.Config
@ -50,7 +57,7 @@ type Feed struct {
loadingOlder *LoadingLock
loadNewer func()
loadOlder func()
Update chan DesktopNotificationType
Update chan DesktopNotificationHolder
apiData *api.RequestData
apiDataMux sync.Mutex
streams []*api.Receiver
@ -98,14 +105,14 @@ func (f *Feed) Delete(id uint) {
}
}
f.items = items
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
func (f *Feed) Clear() {
f.itemsMux.Lock()
defer f.itemsMux.Unlock()
f.items = []api.Item{}
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
func (f *Feed) Item(index int) (api.Item, error) {
@ -123,7 +130,7 @@ func (f *Feed) Item(index int) (api.Item, error) {
return filtered[index], nil
}
func (f *Feed) Updated(nt DesktopNotificationType) {
func (f *Feed) Updated(nt DesktopNotificationHolder) {
if len(f.Update) > 0 {
return
}
@ -143,7 +150,7 @@ func (f *Feed) LoadNewer() {
return
}
f.loadNewer()
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
f.loadingNewer.last = time.Now()
f.loadingNewer.mux.Unlock()
}
@ -161,7 +168,7 @@ func (f *Feed) LoadOlder() {
return
}
f.loadOlder()
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
f.loadingOlder.last = time.Now()
f.loadingOlder.mux.Unlock()
}
@ -192,7 +199,7 @@ func (f *Feed) singleNewerSearch(fn apiSearchFunc, search string) {
f.itemsMux.Lock()
if len(items) > 0 {
f.items = append(items, f.items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
}
@ -205,7 +212,7 @@ func (f *Feed) singleThread(fn apiThreadFunc, status *mastodon.Status) {
f.itemsMux.Lock()
if len(items) > 0 {
f.items = append(items, f.items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
}
@ -218,7 +225,7 @@ func (f *Feed) singleHistory(fn apiHistoryFunc, status *mastodon.Status) {
f.itemsMux.Lock()
if len(items) > 0 {
f.items = append(items, f.items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
}
@ -251,7 +258,7 @@ func (f *Feed) normalNewer(fn apiFunc) {
}
}
f.items = append(items, f.items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
f.apiDataMux.Unlock()
@ -280,7 +287,70 @@ func (f *Feed) normalOlder(fn apiFunc) {
f.apiData.MaxID = item.Item.ID
}
f.items = append(f.items, items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
f.apiDataMux.Unlock()
}
func (f *Feed) normalNewerNotification(fn apiFuncNotification, nth []config.NotificationToHide) {
pg := mastodon.Pagination{}
f.apiDataMux.Lock()
if f.apiData.MinID != mastodon.ID("") {
pg.MinID = f.apiData.MinID
}
items, err := fn(nth, &pg)
if err != nil {
f.apiDataMux.Unlock()
return
}
f.itemsMux.Lock()
if len(items) > 0 {
switch item := items[0].Raw().(type) {
case *mastodon.Status:
f.apiData.MinID = item.ID
case *api.NotificationData:
f.apiData.MinID = item.Item.ID
}
if f.apiData.MaxID == mastodon.ID("") {
switch item := items[len(items)-1].Raw().(type) {
case *mastodon.Status:
f.apiData.MaxID = item.ID
case *api.NotificationData:
f.apiData.MaxID = item.Item.ID
}
}
f.items = append(items, f.items...)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
f.apiDataMux.Unlock()
}
func (f *Feed) normalOlderNotification(fn apiFuncNotification, nth []config.NotificationToHide) {
pg := mastodon.Pagination{}
f.apiDataMux.Lock()
if f.apiData.MaxID == mastodon.ID("") {
f.apiDataMux.Unlock()
f.loadNewer()
return
}
pg.MaxID = f.apiData.MaxID
items, err := fn(nth, &pg)
if err != nil {
f.apiDataMux.Unlock()
return
}
f.itemsMux.Lock()
if len(items) > 0 {
switch item := items[len(items)-1].Raw().(type) {
case *mastodon.Status:
f.apiData.MaxID = item.ID
case *api.NotificationData:
f.apiData.MaxID = item.Item.ID
}
f.items = append(f.items, items...)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
f.apiDataMux.Unlock()
@ -302,7 +372,7 @@ func (f *Feed) newerSearchPG(fn apiSearchPGFunc, search string) {
item := items[0].Raw().(*mastodon.Status)
f.apiData.MinID = item.ID
f.items = append(items, f.items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
if f.apiData.MaxID == mastodon.ID("") {
item = items[len(items)-1].Raw().(*mastodon.Status)
f.apiData.MaxID = item.ID
@ -331,7 +401,7 @@ func (f *Feed) olderSearchPG(fn apiSearchPGFunc, search string) {
item := items[len(items)-1].Raw().(*mastodon.Status)
f.apiData.MaxID = item.ID
f.items = append(f.items, items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
f.apiDataMux.Unlock()
@ -353,7 +423,7 @@ func (f *Feed) normalNewerUser(fn apiIDFunc, id mastodon.ID) {
item := items[0].Raw().(*mastodon.Status)
f.apiData.MinID = item.ID
f.items = append(items, f.items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
if f.apiData.MaxID == mastodon.ID("") {
item = items[len(items)-1].Raw().(*mastodon.Status)
f.apiData.MaxID = item.ID
@ -382,7 +452,7 @@ func (f *Feed) normalOlderUser(fn apiIDFunc, id mastodon.ID) {
item := items[len(items)-1].Raw().(*mastodon.Status)
f.apiData.MaxID = item.ID
f.items = append(f.items, items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
f.apiDataMux.Unlock()
@ -404,7 +474,7 @@ func (f *Feed) normalNewerID(fn apiIDFunc, id mastodon.ID) {
item := items[0].Raw().(*mastodon.Status)
f.apiData.MinID = item.ID
f.items = append(items, f.items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
if f.apiData.MaxID == mastodon.ID("") {
item = items[len(items)-1].Raw().(*mastodon.Status)
f.apiData.MaxID = item.ID
@ -433,7 +503,7 @@ func (f *Feed) normalOlderID(fn apiIDFunc, id mastodon.ID) {
item := items[len(items)-1].Raw().(*mastodon.Status)
f.apiData.MaxID = item.ID
f.items = append(f.items, items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
f.apiDataMux.Unlock()
@ -447,7 +517,7 @@ func (f *Feed) normalEmpty(fn apiEmptyFunc) {
f.itemsMux.Lock()
if len(items) > 0 {
f.items = append(f.items, items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
}
@ -473,7 +543,7 @@ func (f *Feed) linkNewer(fn apiFunc) {
f.itemsMux.Lock()
if len(items) > 0 {
f.items = append(items, f.items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
}
@ -498,7 +568,7 @@ func (f *Feed) linkOlder(fn apiFunc) {
f.itemsMux.Lock()
if len(items) > 0 {
f.items = append(f.items, items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
}
@ -524,7 +594,7 @@ func (f *Feed) linkNewerID(fn apiIDFunc, id mastodon.ID) {
f.itemsMux.Lock()
if len(items) > 0 {
f.items = append(items, f.items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
}
@ -549,7 +619,7 @@ func (f *Feed) linkOlderID(fn apiIDFunc, id mastodon.ID) {
f.itemsMux.Lock()
if len(items) > 0 {
f.items = append(f.items, items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
}
@ -575,7 +645,7 @@ func (f *Feed) linkNewerIDdata(fn apiIDFuncData, id mastodon.ID, data interface{
f.itemsMux.Lock()
if len(items) > 0 {
f.items = append(items, f.items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
}
@ -600,7 +670,7 @@ func (f *Feed) linkOlderIDdata(fn apiIDFuncData, id mastodon.ID, data interface{
f.itemsMux.Lock()
if len(items) > 0 {
f.items = append(f.items, items...)
f.Updated(DeskstopNotificationNone)
f.Updated(DesktopNotificationHolder{Type: DesktopNotificationNone})
}
f.itemsMux.Unlock()
}
@ -630,7 +700,9 @@ func (f *Feed) startStream(rec *api.Receiver, timeline string, err error) {
}
if !found {
f.items = append([]api.Item{s}, f.items...)
f.Updated(DesktopNotificationPost)
f.Updated(DesktopNotificationHolder{
Type: DesktopNotificationPost,
})
f.apiData.MinID = t.Status.ID
}
f.itemsMux.Unlock()
@ -648,6 +720,40 @@ func (f *Feed) startStreamNotification(rec *api.Receiver, timeline string, err e
for e := range rec.Ch {
switch t := e.(type) {
case *mastodon.NotificationEvent:
switch t.Notification.Type {
case "follow":
if slices.Contains(f.config.General.NotificationsToHide, config.HideFollow) {
continue
}
case "follow_request":
if slices.Contains(f.config.General.NotificationsToHide, config.HideFollowRequest) {
continue
}
case "favourite":
if slices.Contains(f.config.General.NotificationsToHide, config.HideFavorite) {
continue
}
case "reblog":
if slices.Contains(f.config.General.NotificationsToHide, config.HideBoost) {
continue
}
case "mention":
if slices.Contains(f.config.General.NotificationsToHide, config.HideMention) {
continue
}
case "update":
if slices.Contains(f.config.General.NotificationsToHide, config.HideEdited) {
continue
}
case "status":
if slices.Contains(f.config.General.NotificationsToHide, config.HideStatus) {
continue
}
case "poll":
if slices.Contains(f.config.General.NotificationsToHide, config.HidePoll) {
continue
}
}
rel, err := f.accountClient.Client.GetAccountRelationships(context.Background(), []string{string(t.Notification.Account.ID)})
if err != nil {
continue
@ -663,7 +769,8 @@ func (f *Feed) startStreamNotification(rec *api.Receiver, timeline string, err e
}, f.accountClient.Filters)
f.itemsMux.Lock()
f.items = append([]api.Item{s}, f.items...)
nft := DeskstopNotificationNone
nft := DesktopNotificationNone
data := t.Notification.Account.DisplayName
switch t.Notification.Type {
case "follow", "follow_request":
nft = DesktopNotificationFollower
@ -680,9 +787,12 @@ func (f *Feed) startStreamNotification(rec *api.Receiver, timeline string, err e
case "poll":
nft = DesktopNotificationPoll
default:
nft = DeskstopNotificationNone
nft = DesktopNotificationNone
}
f.Updated(nft)
f.Updated(DesktopNotificationHolder{
Type: nft,
Data: data,
})
f.itemsMux.Unlock()
}
}
@ -699,7 +809,7 @@ func newFeed(ac *api.AccountClient, ft config.FeedType, cnf *config.Config, show
loadNewer: func() {},
loadOlder: func() {},
apiData: &api.RequestData{},
Update: make(chan DesktopNotificationType, 1),
Update: make(chan DesktopNotificationHolder, 1),
loadingNewer: &LoadingLock{},
loadingOlder: &LoadingLock{},
showBoosts: showBoosts,
@ -764,8 +874,12 @@ func NewConversations(ac *api.AccountClient, cnf *config.Config) *Feed {
func NewNotifications(ac *api.AccountClient, cnf *config.Config, showBoosts bool, showReplies bool) *Feed {
feed := newFeed(ac, config.Notifications, cnf, showBoosts, showReplies)
feed.loadNewer = func() { feed.normalNewer(feed.accountClient.GetNotifications) }
feed.loadOlder = func() { feed.normalOlder(feed.accountClient.GetNotifications) }
feed.loadNewer = func() {
feed.normalNewerNotification(feed.accountClient.GetNotifications, cnf.General.NotificationsToHide)
}
feed.loadOlder = func() {
feed.normalOlderNotification(feed.accountClient.GetNotifications, cnf.General.NotificationsToHide)
}
feed.startStreamNotification(feed.accountClient.NewHomeStream())
feed.close = func() {
for _, s := range feed.streams {

2
go.mod

@ -3,7 +3,7 @@ module github.com/RasmusLindroth/tut
go 1.18
require (
github.com/RasmusLindroth/go-mastodon v0.0.14
github.com/RasmusLindroth/go-mastodon v0.0.16
github.com/atotto/clipboard v0.1.4
github.com/gdamore/tcell/v2 v2.5.3
github.com/gen2brain/beeep v0.0.0-20220909211152-5a9ec94374f6

14
go.sum

@ -1,5 +1,5 @@
github.com/RasmusLindroth/go-mastodon v0.0.14 h1:lmJEgXpYC7uS/8xEwg6yQ+blQ2iyXE4K0xWJitVmI+U=
github.com/RasmusLindroth/go-mastodon v0.0.14/go.mod h1:Lr6n8V1U2b+9P89YZKsICkNc+oNeJXkygY7raei9SXE=
github.com/RasmusLindroth/go-mastodon v0.0.16 h1:87lm4+TE6cNguL/gbfHDYqbOd1Jblnpy8J0l1O3/1wo=
github.com/RasmusLindroth/go-mastodon v0.0.16/go.mod h1:Lr6n8V1U2b+9P89YZKsICkNc+oNeJXkygY7raei9SXE=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@ -38,8 +38,6 @@ github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvI
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/tview v0.0.0-20221128165837-db36428c92d9 h1:ccTgRxA37ypj3q8zB8G4k3xGPfBbIaMwrf3Yw6k50NY=
github.com/rivo/tview v0.0.0-20221128165837-db36428c92d9/go.mod h1:YX2wUZOcJGOIycErz2s9KvDaP0jnWwRCirQMPLPpQ+Y=
github.com/rivo/tview v0.0.0-20221212150847-19d943d59543 h1:qu4/1SXI23subKkH50FN7t6r0tPg7i7jI48M5kZ2qEE=
github.com/rivo/tview v0.0.0-20221212150847-19d943d59543/go.mod h1:YX2wUZOcJGOIycErz2s9KvDaP0jnWwRCirQMPLPpQ+Y=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@ -60,26 +58,18 @@ github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJ
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
golang.org/x/exp v0.0.0-20221212164502-fae10dda9338 h1:OvjRkcNHnf6/W5FZXSxODbxwD+X7fspczG7Jn/xQVD4=
golang.org/x/exp v0.0.0-20221212164502-fae10dda9338/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

2
main.go

@ -8,7 +8,7 @@ import (
"github.com/rivo/tview"
)
const version = "1.0.25"
const version = "1.0.26"
func main() {
util.SetTerminalTitle("tut")

5
ui/cmdbar.go

@ -206,6 +206,9 @@ func (c *CmdBar) DoneFunc(key tcell.Key) {
NewUserSearchFeed(c.tutView, user),
)
c.Back()
case ":stick-to-top":
c.tutView.ToggleStickToTop()
c.Back()
case ":follow-tag":
if len(parts) < 2 {
break
@ -239,7 +242,7 @@ func (c *CmdBar) DoneFunc(key tcell.Key) {
func (c *CmdBar) Autocomplete(curr string) []string {
var entries []string
words := strings.Split(":blocking,:boosts,:bookmarks,:clear-notifications,:compose,:favorites,:favorited,:follow-tag,:followers,:following,:help,:h,:history,:lists,:list-placement,:list-split,:muting,:newer,:preferences,:profile,:proportions,:requests,:saved,:tag,:timeline,:tl,:unfollow-tag,:user,:window,:quit,:q", ",")
words := strings.Split(":blocking,:boosts,:bookmarks,:clear-notifications,:compose,:favorites,:favorited,:follow-tag,:followers,:following,:help,:h,:history,:lists,:list-placement,:list-split,:muting,:newer,:preferences,:profile,:proportions,:requests,:saved,:stick-to-top,:tag,:timeline,:tl,:unfollow-tag,:user,:window,:quit,:q", ",")
if curr == "" {
return entries
}

4
ui/commands.go

@ -265,3 +265,7 @@ func (tv *TutView) ClearNotificationsCommand() {
}
}
}
func (tv *TutView) ToggleStickToTop() {
tv.tut.Config.General.StickToTop = !tv.tut.Config.General.StickToTop
}

12
ui/feed.go

@ -88,26 +88,26 @@ func (f *Feed) DrawContent() {
func (f *Feed) update() {
for nft := range f.Data.Update {
switch nft {
switch nft.Type {
case feed.DesktopNotificationFollower:
if f.tutView.tut.Config.NotificationConfig.NotificationFollower {
beeep.Notify("New follower", "", "")
beeep.Notify(fmt.Sprintf("%s follows you", nft.Data), "", "")
}
case feed.DesktopNotificationFavorite:
if f.tutView.tut.Config.NotificationConfig.NotificationFavorite {
beeep.Notify("Favorited your toot", "", "")
beeep.Notify(fmt.Sprintf("%s favorited your toot", nft.Data), "", "")
}
case feed.DesktopNotificationMention:
if f.tutView.tut.Config.NotificationConfig.NotificationMention {
beeep.Notify("Mentioned you", "", "")
beeep.Notify(fmt.Sprintf("%s mentioned you", nft.Data), "", "")
}
case feed.DesktopNotificationUpdate:
if f.tutView.tut.Config.NotificationConfig.NotificationUpdate {
beeep.Notify("Changed their toot", "", "")
beeep.Notify(fmt.Sprintf("%s changed their toot", nft.Data), "", "")
}
case feed.DesktopNotificationBoost:
if f.tutView.tut.Config.NotificationConfig.NotificationBoost {
beeep.Notify("Boosted your toot", "", "")
beeep.Notify(fmt.Sprintf("%s boosted your toot", nft.Data), "", "")
}
case feed.DesktopNotificationPoll:
if f.tutView.tut.Config.NotificationConfig.NotificationPoll {

2
ui/input.go

@ -148,6 +148,8 @@ func (tv *TutView) InputLeaderKey(event *tcell.EventKey) *tcell.EventKey {
tv.LoadNewerCommand()
case config.LeaderLists:
tv.ListsCommand()
case config.LeaderStickToTop:
tv.ToggleStickToTop()
case config.LeaderTag:
tv.TagCommand(subaction)
case config.LeaderTags:

2
ui/item.go

@ -101,7 +101,7 @@ func DrawItem(tv *TutView, item api.Item, main *tview.TextView, controls *tview.
case config.ListUsersIn:
drawUser(tv, item.Raw().(*api.User), main, controls, "", InputUserListDelete)
default:
drawUser(tv, item.Raw().(*api.User), main, controls, "", InputUserFollowRequest)
drawUser(tv, item.Raw().(*api.User), main, controls, "", InputUserNormal)
}
case api.NotificationType:
drawNotification(tv, item, item.Raw().(*api.NotificationData), main, controls)

2
ui/item_user.go

@ -136,7 +136,7 @@ func drawUser(tv *TutView, data *api.User, main *tview.TextView, controls *tview
if main != nil {
if additional != "" {
additional = fmt.Sprintf("%s\n\n", config.SublteText(tv.tut.Config, additional))
additional = fmt.Sprintf("%s\n\n", config.SublteText(tv.tut.Config, tview.Escape(additional)))
}
main.SetText(additional + output.String())
}

38
ui/media.go

@ -20,12 +20,14 @@ func downloadFile(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
os.Remove(f.Name())
return "", err
}
defer resp.Body.Close()
_, err = io.Copy(f, resp.Body)
if err != nil {
os.Remove(f.Name())
return "", nil
}
@ -40,7 +42,6 @@ func openAvatar(tv *TutView, user mastodon.Account) {
)
return
}
tv.FileList = append(tv.FileList, f)
openMediaType(tv, []string{f}, "image")
}
@ -56,15 +57,17 @@ func reverseFiles(filenames []string) []string {
}
type runProgram struct {
Name string
Args []string
Terminal bool
Name string
Filenames []string
Args []string
Terminal bool
}
func newRunProgram(name string, args ...string) runProgram {
func newRunProgram(name string, filenames []string, args ...string) runProgram {
return runProgram{
Name: name,
Args: args,
Name: name,
Filenames: filenames,
Args: args,
}
}
@ -80,7 +83,7 @@ func openMediaType(tv *TutView, filenames []string, mediaType string) {
if mc.ImageSingle {
for _, f := range filenames {
args := append(mc.ImageArgs, f)
c := newRunProgram(mc.ImageViewer, args...)
c := newRunProgram(mc.ImageViewer, []string{f}, args...)
if mc.ImageTerminal {
terminal = append(terminal, c)
} else {
@ -89,7 +92,7 @@ func openMediaType(tv *TutView, filenames []string, mediaType string) {
}
} else {
args := append(mc.ImageArgs, filenames...)
c := newRunProgram(mc.ImageViewer, args...)
c := newRunProgram(mc.ImageViewer, filenames, args...)
if mc.ImageTerminal {
terminal = append(terminal, c)
} else {
@ -103,7 +106,7 @@ func openMediaType(tv *TutView, filenames []string, mediaType string) {
if mc.VideoSingle {
for _, f := range filenames {
args := append(mc.VideoArgs, f)
c := newRunProgram(mc.VideoViewer, args...)
c := newRunProgram(mc.VideoViewer, []string{f}, args...)
if mc.VideoTerminal {
terminal = append(terminal, c)
} else {
@ -112,7 +115,7 @@ func openMediaType(tv *TutView, filenames []string, mediaType string) {
}
} else {
args := append(mc.VideoArgs, filenames...)
c := newRunProgram(mc.VideoViewer, args...)
c := newRunProgram(mc.VideoViewer, filenames, args...)
if mc.VideoTerminal {
terminal = append(terminal, c)
} else {
@ -126,7 +129,7 @@ func openMediaType(tv *TutView, filenames []string, mediaType string) {
if mc.AudioSingle {
for _, f := range filenames {
args := append(mc.AudioArgs, f)
c := newRunProgram(mc.AudioViewer, args...)
c := newRunProgram(mc.AudioViewer, []string{f}, args...)
if mc.AudioTerminal {
terminal = append(terminal, c)
} else {
@ -135,7 +138,7 @@ func openMediaType(tv *TutView, filenames []string, mediaType string) {
}
} else {
args := append(mc.AudioArgs, filenames...)
c := newRunProgram(mc.AudioViewer, args...)
c := newRunProgram(mc.AudioViewer, filenames, args...)
if mc.AudioTerminal {
terminal = append(terminal, c)
} else {
@ -146,10 +149,18 @@ func openMediaType(tv *TutView, filenames []string, mediaType string) {
go func() {
for _, ext := range external {
exec.Command(ext.Name, ext.Args...).Run()
deleteFiles(ext.Filenames)
}
}()
for _, term := range terminal {
openInTerminal(tv, term.Name, term.Args...)
deleteFiles(term.Filenames)
}
}
func deleteFiles(filenames []string) {
for _, filename := range filenames {
os.Remove(filename)
}
}
@ -178,7 +189,6 @@ func openMedia(tv *TutView, status *mastodon.Status) {
files = append(files, f)
}
openMediaType(tv, files, key)
tv.FileList = append(tv.FileList, files...)
tv.ShouldSync()
}
}

1
ui/styled_elements.go

@ -79,6 +79,7 @@ func NewInputField(cnf *config.Config) *tview.InputField {
i := tview.NewInputField()
i.SetBackgroundColor(cnf.Style.Background)
i.SetFieldBackgroundColor(cnf.Style.Background)
i.SetFieldTextColor((cnf.Style.CommandText))
selected := tcell.Style{}.
Background(cnf.Style.AutocompleteSelectedBackground).

10
ui/tutview.go

@ -54,14 +54,9 @@ type TutView struct {
PreferenceView *PreferenceView
HelpView *HelpView
ModalView *ModalView
FileList []string
}
func (tv *TutView) CleanExit(code int) {
for _, f := range tv.FileList {
os.Remove(f)
}
os.Exit(code)
}
@ -102,9 +97,8 @@ func (l *Leader) Content() string {
func NewTutView(t *Tut, accs *auth.AccountData, selectedUser string) *TutView {
tv := &TutView{
tut: t,
View: tview.NewPages(),
FileList: []string{},
tut: t,
View: tview.NewPages(),
}
tv.Leader = NewLeader(tv)
tv.Shared = NewShared(tv)

Loading…
Cancel
Save