diff --git a/README.md b/README.md index 4e2c999..cb23631 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ # Tut - a Mastodon TUI +[![Release](https://badgen.net/github/release/RasmusLindroth/tut)](https://github.com/RasmusLindroth/tut/releases) +[![web](https://badgen.net/badge/web/tut.anv.nu/f92672)](https://tut.anv.nu) +[![tut](https://badgen.net/badge/AUR/tut/08c)](https://aur.archlinux.org/packages/tut) +[![tut-bin](https://badgen.net/badge/AUR/tut-bin/08c)](https://aur.archlinux.org/packages/tut-bin) +[![@tut](https://badgen.net/mastodon/follow/tut@fosstodon.org)](https://fosstodon.org/@tut) A TUI for Mastodon with vim inspired keys. The program has most of the features you can find in the web client. @@ -8,6 +13,7 @@ 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). ![Preview](./images/preview.png "Preview") +![Preview 2](./images/preview2.png "Preview 2") ## Table of contents @@ -85,6 +91,7 @@ Head over to https://github.com/RasmusLindroth/tut/releases You can find it in the Arch User Repository (AUR). I'm the maintainer there. https://aur.archlinux.org/packages/tut/ +https://aur.archlinux.org/packages/tut-bin/ You can also use `tut-mastodon`. Currently `aur/tut` collides with a package named `tut` if you're running Manjaro ARM. So if you face the same problem you @@ -127,7 +134,7 @@ cd tut # Build or install -# Install (usally /home/user/go/bin) +# Install (usually /home/user/go/bin) go install # Build (same directory i.e. ./ ) diff --git a/api/feed.go b/api/feed.go index d8776dd..b28832e 100644 --- a/api/feed.go +++ b/api/feed.go @@ -15,10 +15,8 @@ func (ac *AccountClient) getStatusSimilar(fn func() ([]*mastodon.Status, error), return items, err } for _, s := range statuses { - item, filtered := NewStatusItem(s, ac.Filters, filter) - if !filtered { - items = append(items, item) - } + item := NewStatusItem(s, ac.Filters, filter, false) + items = append(items, item) } return items, nil } @@ -89,12 +87,10 @@ func (ac *AccountClient) GetNotifications(pg *mastodon.Pagination) ([]Item, erro for _, n := range notifications { for _, r := range rel { if n.Account.ID == r.ID { - item, filtered := NewNotificationItem(n, &User{ + item := NewNotificationItem(n, &User{ Data: &n.Account, Relation: r, }, ac.Filters) - if !filtered { - items = append(items, item) - } + items = append(items, item) break } } @@ -102,29 +98,20 @@ func (ac *AccountClient) GetNotifications(pg *mastodon.Pagination) ([]Item, erro return items, nil } -func (ac *AccountClient) GetThread(status *mastodon.Status) ([]Item, int, error) { +func (ac *AccountClient) GetThread(status *mastodon.Status) ([]Item, error) { var items []Item statuses, err := ac.Client.GetStatusContext(context.Background(), status.ID) if err != nil { - return items, 0, err + return items, err } for _, s := range statuses.Ancestors { - item, filtered := NewStatusItem(s, ac.Filters, "thread") - if !filtered { - items = append(items, item) - } - } - item, filtered := NewStatusItem(status, ac.Filters, "thread") - if !filtered { - items = append(items, item) + items = append(items, NewStatusItem(s, ac.Filters, "thread", false)) } + items = append(items, NewStatusItem(status, ac.Filters, "thread", false)) for _, s := range statuses.Descendants { - item, filtered := NewStatusItem(s, ac.Filters, "thread") - if !filtered { - items = append(items, item) - } + items = append(items, NewStatusItem(s, ac.Filters, "thread", false)) } - return items, len(statuses.Ancestors), nil + return items, nil } func (ac *AccountClient) GetFavorites(pg *mastodon.Pagination) ([]Item, error) { @@ -148,10 +135,8 @@ func (ac *AccountClient) GetConversations(pg *mastodon.Pagination) ([]Item, erro return items, err } for _, c := range conversations { - item, filtered := NewStatusItem(c.LastStatus, ac.Filters, "thread") - if !filtered { - items = append(items, item) - } + item := NewStatusItem(c.LastStatus, ac.Filters, "thread", false) + items = append(items, item) } return items, nil } @@ -240,10 +225,21 @@ func (ac *AccountClient) GetUser(pg *mastodon.Pagination, id mastodon.ID) ([]Ite return items, err } for _, s := range statuses { - item, filtered := NewStatusItem(s, ac.Filters, "account") - if !filtered { - items = append(items, item) - } + item := NewStatusItem(s, ac.Filters, "account", false) + items = append(items, item) + } + return items, nil +} + +func (ac *AccountClient) GetUserPinned(id mastodon.ID) ([]Item, error) { + var items []Item + statuses, err := ac.Client.GetAccountPinnedStatuses(context.Background(), id) + if err != nil { + return items, err + } + for _, s := range statuses { + item := NewStatusItem(s, ac.Filters, "account", true) + items = append(items, item) } return items, nil } @@ -267,10 +263,8 @@ func (ac *AccountClient) GetListStatuses(pg *mastodon.Pagination, id mastodon.ID return items, err } for _, s := range statuses { - item, filtered := NewStatusItem(s, ac.Filters, "home") - if !filtered { - items = append(items, item) - } + item := NewStatusItem(s, ac.Filters, "home", false) + items = append(items, item) } return items, nil } diff --git a/api/item.go b/api/item.go index ef53ea9..5360fc2 100644 --- a/api/item.go +++ b/api/item.go @@ -26,12 +26,19 @@ type Item interface { ShowSpoiler() bool Raw() interface{} URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) + Filtered() (bool, string) + Pinned() bool } -func NewStatusItem(item *mastodon.Status, filters []*mastodon.Filter, timeline string) (sitem Item, filtered bool) { - filtered = false +type filtered struct { + inUse bool + name string +} + +func NewStatusItem(item *mastodon.Status, filters []*mastodon.Filter, timeline string, pinned bool) (sitem Item) { + filtered := filtered{inUse: false} if item == nil { - return &StatusItem{id: newID(), item: item, showSpoiler: false}, false + return &StatusItem{id: newID(), item: item, showSpoiler: false, filtered: filtered, pinned: pinned} } s := util.StatusOrReblog(item) content := s.Content @@ -67,30 +74,35 @@ func NewStatusItem(item *mastodon.Status, filters []*mastodon.Filter, timeline s filter := strings.Split(strings.ToLower(f.Phrase), " ") for i := 0; i+len(filter)-1 < len(stripped); i++ { if strings.ToLower(f.Phrase) == strings.Join(stripped[i:i+len(filter)], " ") { - filtered = true + filtered.inUse = true + filtered.name = f.Phrase break } } } else { if strings.Contains(s.Content, strings.ToLower(f.Phrase)) { - filtered = true + filtered.inUse = true + filtered.name = f.Phrase } if strings.Contains(s.SpoilerText, strings.ToLower(f.Phrase)) { - filtered = true + filtered.inUse = true + filtered.name = f.Phrase } } - if filtered { + if filtered.inUse { break } } - sitem = &StatusItem{id: newID(), item: item, showSpoiler: false} - return sitem, filtered + sitem = &StatusItem{id: newID(), item: item, showSpoiler: false, filtered: filtered, pinned: pinned} + return sitem } type StatusItem struct { id uint item *mastodon.Status showSpoiler bool + filtered filtered + pinned bool } func (s *StatusItem) ID() uint { @@ -141,6 +153,14 @@ func (s *StatusItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int return realUrls, status.Mentions, status.Tags, length } +func (s *StatusItem) Filtered() (bool, string) { + return s.filtered.inUse, s.filtered.name +} + +func (s *StatusItem) Pinned() bool { + return s.pinned +} + func NewUserItem(item *User, profile bool) Item { return &UserItem{id: newID(), item: item, profile: profile} } @@ -185,8 +205,16 @@ func (u *UserItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) return urls, []mastodon.Mention{}, []mastodon.Tag{}, len(urls) } -func NewNotificationItem(item *mastodon.Notification, user *User, filters []*mastodon.Filter) (nitem Item, filtred bool) { - status, filtred := NewStatusItem(item.Status, filters, "notifications") +func (s *UserItem) Filtered() (bool, string) { + return false, "" +} + +func (u *UserItem) Pinned() bool { + return false +} + +func NewNotificationItem(item *mastodon.Notification, user *User, filters []*mastodon.Filter) (nitem Item) { + status := NewStatusItem(item.Status, filters, "notifications", false) nitem = &NotificationItem{ id: newID(), item: item, @@ -195,7 +223,7 @@ func NewNotificationItem(item *mastodon.Notification, user *User, filters []*mas status: status, } - return nitem, filtred + return nitem } type NotificationItem struct { @@ -240,6 +268,14 @@ func (n *NotificationItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Ta return nil, nil, nil, 0 } +func (n *NotificationItem) Filtered() (bool, string) { + return false, "" +} + +func (n *NotificationItem) Pinned() bool { + return false +} + func NewListsItem(item *mastodon.List) Item { return &ListItem{id: newID(), item: item, showSpoiler: true} } @@ -272,3 +308,11 @@ func (s *ListItem) Raw() interface{} { func (s *ListItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) { return nil, nil, nil, 0 } + +func (s *ListItem) Filtered() (bool, string) { + return false, "" +} + +func (n *ListItem) Pinned() bool { + return false +} diff --git a/config.example.ini b/config.example.ini index ad5ef92..caa1fcb 100644 --- a/config.example.ini +++ b/config.example.ini @@ -107,6 +107,10 @@ show-icons=true # default=false short-hints=false +# If you want to display the filter that filtered a toot. +# default=true +show-filter-phrase=true + # If you want to show a message in the cmdbar on how to access the help text. # default=true show-help=true @@ -132,7 +136,7 @@ leader-key= leader-timeout=1000 # You set actions for the leader-key with one or more leader-action. It consists -# of two parts first the action then the shortcut. And they're seperated by a +# of two parts first the action then the shortcut. And they're separated by a # comma. # # Available commands: home, direct, local, federated, compose, blocking, @@ -314,7 +318,7 @@ posts=false # # You can also use xrdb colors like this xrdb:color1 The program will use colors # prefixed with an * first then look for URxvt or XTerm if it can't find any -# color prefixed with an asterik. If you don't want tut to guess the prefix you +# color prefixed with an asterisk. If you don't want tut to guess the prefix you # can set the prefix yourself. If the xrdb color can't be found a preset color # will be used. You'll have to set theme=none for this to work. @@ -326,7 +330,7 @@ xrdb-prefix=guess # available on the URL below. If a theme is named "nord.ini" you just write # theme=nord # -# https://github.com/RasmusLindroth/tut/tree/master/themes +# https://github.com/RasmusLindroth/tut/tree/master/config/themes # # If you want to use your own theme set theme to none then you can create your # own theme below diff --git a/config/config.go b/config/config.go index 006e39a..be46639 100644 --- a/config/config.go +++ b/config/config.go @@ -85,29 +85,29 @@ type Timeline struct { } type General struct { - Confirmation bool - DateTodayFormat string - DateFormat string - DateRelative int - MaxWidth int - StartTimeline feed.FeedType - NotificationFeed bool - QuoteReply bool - CharLimit int - ShortHints bool - ListPlacement ListPlacement - ListSplit ListSplit - HideNotificationText bool - ListProportion int - ContentProportion int - ShowIcons bool - ShowHelp bool - RedrawUI bool - LeaderKey rune - LeaderTimeout int64 - LeaderActions []LeaderAction - TimelineName bool - Timelines []Timeline + Confirmation bool + DateTodayFormat string + DateFormat string + DateRelative int + MaxWidth int + StartTimeline feed.FeedType + NotificationFeed bool + QuoteReply bool + CharLimit int + ShortHints bool + ShowFilterPhrase bool + ListPlacement ListPlacement + ListSplit ListSplit + ListProportion int + ContentProportion int + ShowIcons bool + ShowHelp bool + RedrawUI bool + LeaderKey rune + LeaderTimeout int64 + LeaderActions []LeaderAction + TimelineName bool + Timelines []Timeline } type Style struct { @@ -576,7 +576,7 @@ func parseGeneral(cfg *ini.File) General { general.CharLimit = cfg.Section("general").Key("char-limit").MustInt(500) general.MaxWidth = cfg.Section("general").Key("max-width").MustInt(0) general.ShortHints = cfg.Section("general").Key("short-hints").MustBool(false) - general.HideNotificationText = cfg.Section("general").Key("hide-notification-text").MustBool(false) + general.ShowFilterPhrase = cfg.Section("general").Key("show-filter-phrase").MustBool(true) general.ShowIcons = cfg.Section("general").Key("show-icons").MustBool(true) general.ShowHelp = cfg.Section("general").Key("show-help").MustBool(true) general.RedrawUI = cfg.Section("general").Key("redraw-ui").MustBool(true) @@ -630,7 +630,7 @@ func parseGeneral(cfg *ini.File) General { for _, l := range lactions { parts := strings.Split(l, ",") if len(parts) != 2 { - fmt.Printf("leader-action must consist of two parts seperated by a comma. Your value is: %s\n", strings.Join(parts, ",")) + fmt.Printf("leader-action must consist of two parts separated by a comma. Your value is: %s\n", strings.Join(parts, ",")) os.Exit(1) } for i, p := range parts { diff --git a/config/default_config.go b/config/default_config.go index e3c4336..644209d 100644 --- a/config/default_config.go +++ b/config/default_config.go @@ -109,6 +109,10 @@ show-icons=true # default=false short-hints=false +# If you want to display the filter that filtered a toot. +# default=true +show-filter-phrase=true + # If you want to show a message in the cmdbar on how to access the help text. # default=true show-help=true @@ -134,7 +138,7 @@ leader-key= leader-timeout=1000 # You set actions for the leader-key with one or more leader-action. It consists -# of two parts first the action then the shortcut. And they're seperated by a +# of two parts first the action then the shortcut. And they're separated by a # comma. # # Available commands: home, direct, local, federated, compose, blocking, @@ -316,7 +320,7 @@ posts=false # # You can also use xrdb colors like this xrdb:color1 The program will use colors # prefixed with an * first then look for URxvt or XTerm if it can't find any -# color prefixed with an asterik. If you don't want tut to guess the prefix you +# color prefixed with an asterisk. If you don't want tut to guess the prefix you # can set the prefix yourself. If the xrdb color can't be found a preset color # will be used. You'll have to set theme=none for this to work. @@ -328,7 +332,7 @@ xrdb-prefix=guess # available on the URL below. If a theme is named "nord.ini" you just write # theme=nord # -# https://github.com/RasmusLindroth/tut/tree/master/themes +# https://github.com/RasmusLindroth/tut/tree/master/config/themes # # If you want to use your own theme set theme to none then you can create your # own theme below diff --git a/feed/feed.go b/feed/feed.go index e25329a..d2a7669 100644 --- a/feed/feed.go +++ b/feed/feed.go @@ -16,7 +16,7 @@ type apiEmptyFunc func() ([]api.Item, error) type apiIDFunc func(pg *mastodon.Pagination, id mastodon.ID) ([]api.Item, error) type apiSearchFunc func(search string) ([]api.Item, error) type apiSearchPGFunc func(pg *mastodon.Pagination, search string) ([]api.Item, error) -type apiThreadFunc func(status *mastodon.Status) ([]api.Item, int, error) +type apiThreadFunc func(status *mastodon.Status) ([]api.Item, error) type FeedType uint @@ -64,6 +64,7 @@ const ( type Feed struct { accountClient *api.AccountClient feedType FeedType + sticky []api.Item items []api.Item itemsMux sync.RWMutex loadingNewer *LoadingLock @@ -85,7 +86,8 @@ func (f *Feed) Type() FeedType { func (f *Feed) List() []api.Item { f.itemsMux.RLock() defer f.itemsMux.RUnlock() - return f.items + r := f.sticky + return append(r, f.items...) } func (f *Feed) Delete(id uint) { @@ -167,6 +169,10 @@ func (f *Feed) Name() string { return f.name } +func (f *Feed) StickyCount() int { + return len(f.sticky) +} + func (f *Feed) singleNewerSearch(fn apiSearchFunc, search string) { items, err := fn(search) if err != nil { @@ -181,7 +187,7 @@ func (f *Feed) singleNewerSearch(fn apiSearchFunc, search string) { } func (f *Feed) singleThread(fn apiThreadFunc, status *mastodon.Status) { - items, _, err := fn(status) + items, err := fn(status) if err != nil { return } @@ -322,12 +328,7 @@ func (f *Feed) normalNewerUser(fn apiIDFunc, id mastodon.ID) { if len(items) > 0 { item := items[0].Raw().(*mastodon.Status) f.apiData.MinID = item.ID - newItems := []api.Item{f.items[0]} - newItems = append(newItems, items...) - if len(f.items) > 1 { - newItems = append(newItems, f.items[1:]...) - } - f.items = newItems + f.items = append(items, f.items...) f.Updated(DekstopNotificationNone) if f.apiData.MaxID == mastodon.ID("") { item = items[len(items)-1].Raw().(*mastodon.Status) @@ -538,13 +539,11 @@ func (f *Feed) startStream(rec *api.Receiver, timeline string, err error) { for e := range f.stream.Ch { switch t := e.(type) { case *mastodon.UpdateEvent: - s, filtered := api.NewStatusItem(t.Status, f.accountClient.Filters, timeline) - if !filtered { - f.itemsMux.Lock() - f.items = append([]api.Item{s}, f.items...) - f.Updated(DesktopNotificationPost) - f.itemsMux.Unlock() - } + s := api.NewStatusItem(t.Status, f.accountClient.Filters, timeline, false) + f.itemsMux.Lock() + f.items = append([]api.Item{s}, f.items...) + f.Updated(DesktopNotificationPost) + f.itemsMux.Unlock() } } }() @@ -567,32 +566,30 @@ func (f *Feed) startStreamNotification(rec *api.Receiver, timeline string, err e log.Fatalln(t.Notification.Account.Acct) continue } - s, filtered := api.NewNotificationItem(t.Notification, + s := api.NewNotificationItem(t.Notification, &api.User{ Data: &t.Notification.Account, Relation: rel[0], }, f.accountClient.Filters) - if !filtered { - f.itemsMux.Lock() - f.items = append([]api.Item{s}, f.items...) - nft := DekstopNotificationNone - switch t.Notification.Type { - case "follow", "follow_request": - nft = DesktopNotificationFollower - case "favourite": - nft = DesktopNotificationFollower - case "reblog": - nft = DesktopNotificationBoost - case "mention": - nft = DesktopNotificationMention - case "status": - nft = DesktopNotificationPost - case "poll": - nft = DesktopNotificationPoll - } - f.Updated(nft) - f.itemsMux.Unlock() + f.itemsMux.Lock() + f.items = append([]api.Item{s}, f.items...) + nft := DekstopNotificationNone + switch t.Notification.Type { + case "follow", "follow_request": + nft = DesktopNotificationFollower + case "favourite": + nft = DesktopNotificationFollower + case "reblog": + nft = DesktopNotificationBoost + case "mention": + nft = DesktopNotificationMention + case "status": + nft = DesktopNotificationPost + case "poll": + nft = DesktopNotificationPoll } + f.Updated(nft) + f.itemsMux.Unlock() } } }() @@ -601,6 +598,7 @@ func (f *Feed) startStreamNotification(rec *api.Receiver, timeline string, err e func newFeed(ac *api.AccountClient, ft FeedType) *Feed { return &Feed{ accountClient: ac, + sticky: make([]api.Item, 0), items: make([]api.Item, 0), feedType: ft, loadNewer: func() {}, @@ -689,7 +687,11 @@ func NewUserSearch(ac *api.AccountClient, search string) *Feed { func NewUserProfile(ac *api.AccountClient, user *api.User) *Feed { feed := newFeed(ac, User) feed.name = user.Data.Acct - feed.items = append(feed.items, api.NewUserItem(user, true)) + feed.sticky = append(feed.sticky, api.NewUserItem(user, true)) + pinned, err := ac.GetUserPinned(user.Data.ID) + if err == nil { + feed.sticky = append(feed.sticky, pinned...) + } feed.loadNewer = func() { feed.normalNewerUser(feed.accountClient.GetUser, user.Data.ID) } feed.loadOlder = func() { feed.normalOlderUser(feed.accountClient.GetUser, user.Data.ID) } diff --git a/go.mod b/go.mod index 9fd2715..6995410 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/RasmusLindroth/tut go 1.17 require ( - github.com/RasmusLindroth/go-mastodon v0.0.7 + github.com/RasmusLindroth/go-mastodon v0.0.8 github.com/atotto/clipboard v0.1.4 github.com/gdamore/tcell/v2 v2.5.1 github.com/gen2brain/beeep v0.0.0-20220518085355-d7852edf42fc @@ -13,8 +13,8 @@ require ( github.com/pelletier/go-toml/v2 v2.0.1 github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8 github.com/rivo/uniseg v0.2.0 - golang.org/x/net v0.0.0-20220526153639-5463443f8c37 - gopkg.in/ini.v1 v1.66.4 + golang.org/x/net v0.0.0-20220531201128-c960675eff93 + gopkg.in/ini.v1 v1.66.6 ) require ( diff --git a/go.sum b/go.sum index 73d11cf..3e6acc1 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/RasmusLindroth/go-mastodon v0.0.7 h1:iGgkkvDrPHTiAyACUehLH5zragSHCUSbhcYdQEBIn48= -github.com/RasmusLindroth/go-mastodon v0.0.7/go.mod h1:4L0oyiNwq1tUoiByczzhSikxR9RiANzELtZgexxKpPM= +github.com/RasmusLindroth/go-mastodon v0.0.8 h1:t2rrbdNgS4h0JhmPNsmUOQBByDxmUPawnERGn6oR2eA= +github.com/RasmusLindroth/go-mastodon v0.0.8/go.mod h1:4L0oyiNwq1tUoiByczzhSikxR9RiANzELtZgexxKpPM= 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= @@ -49,8 +49,8 @@ github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+a github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8= -golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA= +golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -71,7 +71,7 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= -gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/images/preview2.png b/images/preview2.png new file mode 100644 index 0000000..f43373e Binary files /dev/null and b/images/preview2.png differ diff --git a/main.go b/main.go index 7f941ca..74f09b9 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,7 @@ import ( "github.com/rivo/tview" ) -const version = "1.0.10" +const version = "1.0.11" func main() { util.MakeDirs() diff --git a/ui/cliview.go b/ui/cliview.go index f2f6f9f..5e711f7 100644 --- a/ui/cliview.go +++ b/ui/cliview.go @@ -41,7 +41,7 @@ func CliView(version string) (newUser bool, selectedUser string) { fmt.Print("\t\tIf two users are named the same. Use full name like tut@fosstodon.org\n\n") fmt.Print("Configuration:\n") - fmt.Printf("\tThe config is located in XDG_CONFIG_HOME/tut/config.ini which usally equals to ~/.config/tut/config.ini.\n") + fmt.Printf("\tThe config is located in XDG_CONFIG_HOME/tut/config.ini which usually equals to ~/.config/tut/config.ini.\n") fmt.Printf("\tThe program will generate the file the first time you run tut. The file has comments which exmplains what each configuration option does.\n\n") fmt.Print("Contact info for issues or questions:\n") diff --git a/ui/feed.go b/ui/feed.go index d4c461d..5260251 100644 --- a/ui/feed.go +++ b/ui/feed.go @@ -14,8 +14,9 @@ import ( ) type FeedList struct { - Text *tview.List - Symbol *tview.List + Text *tview.List + Symbol *tview.List + stickyCount int } func (fl *FeedList) InFocus(style config.Style) { @@ -136,7 +137,7 @@ func NewHomeFeed(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -151,7 +152,7 @@ func NewFederatedFeed(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -166,7 +167,7 @@ func NewLocalFeed(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -181,7 +182,7 @@ func NewNotificationFeed(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -196,7 +197,7 @@ func NewThreadFeed(tv *TutView, item api.Item) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } for i, s := range f.List() { @@ -218,7 +219,7 @@ func NewConversationsFeed(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -237,7 +238,7 @@ func NewUserFeed(tv *TutView, item api.Item) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -252,7 +253,7 @@ func NewUserSearchFeed(tv *TutView, search string) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } for _, s := range f.List() { @@ -271,7 +272,7 @@ func NewTagFeed(tv *TutView, search string) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -285,7 +286,7 @@ func NewListsFeed(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -300,7 +301,7 @@ func NewListFeed(tv *TutView, l *mastodon.List) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -315,7 +316,7 @@ func NewFavoritedFeed(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } @@ -330,7 +331,7 @@ func NewBookmarksFeed(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -345,7 +346,7 @@ func NewFavoritesStatus(tv *TutView, id mastodon.ID) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -360,7 +361,7 @@ func NewBoosts(tv *TutView, id mastodon.ID) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -375,7 +376,7 @@ func NewFollowers(tv *TutView, id mastodon.ID) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -390,7 +391,7 @@ func NewFollowing(tv *TutView, id mastodon.ID) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -405,7 +406,7 @@ func NewBlocking(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -420,7 +421,7 @@ func NewMuting(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -435,7 +436,7 @@ func NewFollowRequests(tv *TutView) *Feed { tutView: tv, Data: f, ListIndex: 0, - List: NewFeedList(tv.tut), + List: NewFeedList(tv.tut, f.StickyCount()), Content: NewFeedContent(tv.tut), } go fd.update() @@ -443,10 +444,11 @@ func NewFollowRequests(tv *TutView) *Feed { return fd } -func NewFeedList(t *Tut) *FeedList { +func NewFeedList(t *Tut, stickyCount int) *FeedList { fl := &FeedList{ - Text: NewList(t.Config), - Symbol: NewList(t.Config), + Text: NewList(t.Config), + Symbol: NewList(t.Config), + stickyCount: stickyCount, } return fl } @@ -476,7 +478,7 @@ func (fl *FeedList) Prev() (loadNewer bool) { } fl.Text.SetCurrentItem(ni) fl.Symbol.SetCurrentItem(ni) - return ni < 4 + return ni-fl.stickyCount < 4 } func (fl *FeedList) Clear() { diff --git a/ui/input.go b/ui/input.go index 2e9aade..aff1b19 100644 --- a/ui/input.go +++ b/ui/input.go @@ -452,7 +452,7 @@ func (tv *TutView) InputStatus(event *tcell.EventKey, item api.Item, status *mas return nil } if tv.tut.Config.Input.StatusUser.Match(event.Key(), event.Rune()) { - id := status.Account.ID + id := sr.Account.ID if nAcc != nil { id = nAcc.ID } @@ -468,7 +468,7 @@ func (tv *TutView) InputStatus(event *tcell.EventKey, item api.Item, status *mas return nil } if tv.tut.Config.Input.StatusYank.Match(event.Key(), event.Rune()) { - copyToClipboard(status.URL) + copyToClipboard(sr.URL) return nil } if tv.tut.Config.Input.StatusToggleSpoiler.Match(event.Key(), event.Rune()) { diff --git a/ui/item.go b/ui/item.go index 3c7dd3c..38f5156 100644 --- a/ui/item.go +++ b/ui/item.go @@ -20,13 +20,20 @@ func DrawListItem(cfg *config.Config, item api.Item) (string, string) { symbol := "" status := s if s.Reblog != nil { - status = s + status = s.Reblog } if status.RepliesCount > 0 { symbol = " ⤶ " } + if item.Pinned() { + symbol = " ! " + } + acc := strings.TrimSpace(s.Account.Acct) + if s.Reblog != nil && cfg.General.ShowIcons { + acc = fmt.Sprintf("♺ %s", acc) + } d := OutputDate(cfg, s.CreatedAt.Local()) - return fmt.Sprintf("%s %s", d, strings.TrimSpace(s.Account.Acct)), symbol + return fmt.Sprintf("%s %s", d, acc), symbol case api.UserType: a := item.Raw().(*api.User) return strings.TrimSpace(a.Data.Acct), "" diff --git a/ui/item_status.go b/ui/item_status.go index c6b9f31..b1a752d 100644 --- a/ui/item_status.go +++ b/ui/item_status.go @@ -71,6 +71,24 @@ type DisplayTootData struct { } func drawStatus(tut *Tut, item api.Item, status *mastodon.Status, main *tview.TextView, controls *tview.TextView, additional string) { + filtered, phrase := item.Filtered() + if filtered { + var output string + if tut.Config.General.ShowFilterPhrase { + output = fmt.Sprintf("Filtered by phrase: %s", tview.Escape(phrase)) + } else { + output = "Filtered." + } + if main != nil { + if additional != "" { + additional = fmt.Sprintf("%s\n\n", config.SublteText(tut.Config, additional)) + } + main.SetText(additional + output) + } + controls.SetText("") + return + } + showSensitive := item.ShowSpoiler() var strippedContent string