diff --git a/README.md b/README.md index 0fd2e09..da429b0 100644 --- a/README.md +++ b/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` diff --git a/api/feed.go b/api/feed.go index 29a4b34..ca633a0 100644 --- a/api/feed.go +++ b/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 } diff --git a/api/stream.go b/api/stream.go index a2d908c..545f3f0 100644 --- a/api/stream.go +++ b/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) } } } diff --git a/config.example.ini b/config.example.ini index 4bc8e0b..eba1710 100644 --- a/config.example.ini +++ b/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 diff --git a/config/config.go b/config/config.go index fe22d80..9d4bd1a 100644 --- a/config/config.go +++ b/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 } diff --git a/config/default_config.go b/config/default_config.go index eb82c7e..e02fbd4 100644 --- a/config/default_config.go +++ b/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 diff --git a/config/help.tmpl b/config/help.tmpl index 561cfb6..1d7ac1f 100644 --- a/config/help.tmpl +++ b/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 diff --git a/feed/feed.go b/feed/feed.go index 06ba599..758b847 100644 --- a/feed/feed.go +++ b/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 { diff --git a/go.mod b/go.mod index 1594b1d..6dc8c96 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 23930b3..1564665 100644 --- a/go.sum +++ b/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= diff --git a/main.go b/main.go index ecfb35d..903635a 100644 --- a/main.go +++ b/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") diff --git a/ui/cmdbar.go b/ui/cmdbar.go index aaf5aa9..e684966 100644 --- a/ui/cmdbar.go +++ b/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 } diff --git a/ui/commands.go b/ui/commands.go index 22a88dc..6deb933 100644 --- a/ui/commands.go +++ b/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 +} diff --git a/ui/feed.go b/ui/feed.go index 6cfa80b..9e1ceb7 100644 --- a/ui/feed.go +++ b/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 { diff --git a/ui/input.go b/ui/input.go index 17f0746..549e298 100644 --- a/ui/input.go +++ b/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: diff --git a/ui/item.go b/ui/item.go index 8051d1a..2a20438 100644 --- a/ui/item.go +++ b/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) diff --git a/ui/item_user.go b/ui/item_user.go index e7d854b..5bdd280 100644 --- a/ui/item_user.go +++ b/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()) } diff --git a/ui/media.go b/ui/media.go index d6cea8a..bd1a46f 100644 --- a/ui/media.go +++ b/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() } } diff --git a/ui/styled_elements.go b/ui/styled_elements.go index 684104e..d3de03f 100644 --- a/ui/styled_elements.go +++ b/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). diff --git a/ui/tutview.go b/ui/tutview.go index 51a0018..3c46e3b 100644 --- a/ui/tutview.go +++ b/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)