package ui import ( "fmt" "strings" "sync" "time" "github.com/RasmusLindroth/go-mastodon" "github.com/RasmusLindroth/tut/api" "github.com/RasmusLindroth/tut/config" "github.com/RasmusLindroth/tut/util" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) func (tv *TutView) Input(event *tcell.EventKey) *tcell.EventKey { if tv.PageFocus != LoginFocus { switch event.Rune() { case ':': tv.SetPage(CmdFocus) case '?': tv.SetPage(HelpFocus) } } if tv.PageFocus != LoginFocus && tv.PageFocus != CmdFocus { event = tv.InputLeaderKey(event) if event == nil { return nil } } if tv.tut.Config.Input.MainNextAccount.Match(event.Key(), event.Rune()) { tv.NextAcct() return nil } if tv.tut.Config.Input.MainPrevAccount.Match(event.Key(), event.Rune()) { tv.NextAcct() return nil } switch tv.PageFocus { case LoginFocus: return tv.InputLoginView(event) case MainFocus: return tv.InputMainView(event) case ViewFocus: return tv.InputViewItem(event) case ComposeFocus: return tv.InputComposeView(event) case PollFocus: return tv.InputPollView(event) case LinkFocus: return tv.InputLinkView(event) case CmdFocus: return tv.InputCmdView(event) case MediaFocus: return tv.InputMedia(event) case MediaAddFocus: return tv.InputMediaAdd(event) case VoteFocus: return tv.InputVote(event) case HelpFocus: return tv.InputHelp(event) case PreferenceFocus: return tv.InputPreference(event) case EditorFocus: return tv.InputEditorView(event) default: return event } } func (tv *TutView) InputLoginView(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { tv.LoginView.Next() return nil } if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { tv.LoginView.Prev() return nil } if tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { tv.LoginView.Selected() return nil } if tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.tut.App.Stop() tv.CleanExit(0) return nil } return event } func (tv *TutView) InputLeaderKey(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.General.LeaderKey == rune(0) { return event } if event.Rune() == tv.tut.Config.General.LeaderKey { tv.Leader.Reset() return nil } else if tv.Leader.IsActive() { if event.Rune() != rune(0) { tv.Leader.AddRune(event.Rune()) } action := config.LeaderNone var subaction string content := tv.Leader.Content() foundFeed := false for i, fh := range tv.Timeline.Feeds { for _, f := range fh.Feeds { if f.Timeline.Shortcut == "" || f.Timeline.Shortcut != content { continue } switch f.Timeline.OnFocus { case config.TimelineFocusPane: tv.FocusFeed(i, nil) case config.TimelineFocusTimeline: tv.FocusFeed(i, f.Timeline) } foundFeed = true tv.Leader.ResetInactive() return nil } } if !foundFeed { for _, tl := range tv.tut.Config.General.Timelines { if tl.Shortcut == "" || tl.Shortcut != content { continue } tv.matchedTimeline(tl) tv.Leader.ResetInactive() return nil } } for _, la := range tv.tut.Config.General.LeaderActions { if la.Shortcut == content { action = la.Command subaction = la.Subaction break } } if action == config.LeaderNone { return nil } switch action { case config.LeaderClearNotifications: tv.ClearNotificationsCommand() case config.LeaderCompose: tv.ComposeCommand() case config.LeaderEdit: tv.EditCommand() case config.LeaderBlocking: tv.BlockingCommand() case config.LeaderFavorited: tv.FavoritedCommand() case config.LeaderHistory: tv.HistoryCommand() case config.LeaderBoosts: tv.BoostsCommand() case config.LeaderFavorites: tv.FavoritesCommand() case config.LeaderFollowing: tv.FollowingCommand() case config.LeaderFollowers: tv.FollowersCommand() case config.LeaderMuting: tv.MutingCommand() case config.LeaderPreferences: tv.PreferencesCommand() case config.LeaderProfile: tv.ProfileCommand() case config.LeaderLoadNewer: tv.LoadNewerCommand() case config.LeaderStickToTop: tv.ToggleStickToTop() case config.LeaderRefetch: tv.RefetchCommand() case config.LeaderTags: tv.TagsCommand() case config.LeaderPane: tv.PaneCommand(subaction) case config.LeaderClosePane: tv.ClosePaneCommand() case config.LeaderMovePaneLeft: tv.MovePaneLeft() case config.LeaderMovePaneRight: tv.MovePaneRight() case config.LeaderMovePaneHome: tv.MovePaneHome() case config.LeaderMovePaneEnd: tv.MovePaneEnd() case config.LeaderListPlacement: switch subaction { case "top": tv.ListPlacementCommand(config.ListPlacementTop) case "right": tv.ListPlacementCommand(config.ListPlacementRight) case "bottom": tv.ListPlacementCommand(config.ListPlacementBottom) case "left": tv.ListPlacementCommand(config.ListPlacementLeft) } case config.LeaderListSplit: switch subaction { case "row": tv.ListSplitCommand(config.ListRow) case "column": tv.ListSplitCommand(config.ListColumn) } case config.LeaderProportions: parts := strings.Split(subaction, " ") if len(parts) == 2 { tv.ProportionsCommand(parts[0], parts[1]) } } tv.Leader.ResetInactive() return nil } return event } func (tv *TutView) InputMainView(event *tcell.EventKey) *tcell.EventKey { switch tv.SubFocus { case ListFocus: return tv.InputMainViewFeed(event) case ContentFocus: return tv.InputMainViewContent(event) default: return event } } func (tv *TutView) matchedTimeline(tl *config.Timeline) { nf := CreateFeed(tv, tl) switch tl.OnCreationClosed { case config.TimelineCreationClosedCurrentPane: tv.Timeline.AddFeed(nf, false) case config.TimelineCreationClosedNewPane: tv.Timeline.AddFeed(nf, true) } } func (tv *TutView) InputMainViewFeed(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.MainHome.Match(event.Key(), event.Rune()) { tv.Timeline.HomeItemFeed() return nil } if tv.tut.Config.Input.MainEnd.Match(event.Key(), event.Rune()) { tv.Timeline.EndItemFeed() return nil } if tv.tut.Config.Input.MainPrevFeed.Match(event.Key(), event.Rune()) { tv.Timeline.PrevFeed() return nil } if tv.tut.Config.Input.MainNextFeed.Match(event.Key(), event.Rune()) { tv.Timeline.NextFeed() return nil } if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { tv.Timeline.NextItemFeed() return nil } if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { tv.Timeline.PrevItemFeed() return nil } if tv.tut.Config.Input.MainPrevPane.Match(event.Key(), event.Rune()) { tv.PrevFeed() return nil } if tv.tut.Config.Input.MainNextPane.Match(event.Key(), event.Rune()) { tv.NextFeed() return nil } if tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { exiting := tv.Timeline.RemoveCurrent(false) if exiting && tv.Timeline.FeedFocusIndex == 0 { tv.ModalView.Run("Do you want to exit tut?", func() { tv.Timeline.RemoveCurrent(true) }) return nil } else if exiting && tv.Timeline.FeedFocusIndex != 0 { tv.FocusFeed(0, nil) } return nil } foundFeed := false for i, fh := range tv.Timeline.Feeds { for _, f := range fh.Feeds { if f.Timeline.Key.Match(event.Key(), event.Rune()) { switch f.Timeline.OnFocus { case config.TimelineFocusPane: tv.FocusFeed(i, nil) case config.TimelineFocusTimeline: tv.FocusFeed(i, f.Timeline) } foundFeed = true return nil } } } if !foundFeed { for _, tl := range tv.tut.Config.General.Timelines { if tl.Key.Match(event.Key(), event.Rune()) { tv.matchedTimeline(tl) return nil } } } if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) { exiting := tv.Timeline.RemoveCurrent(false) if exiting && tv.Timeline.FeedFocusIndex != 0 { tv.FocusFeed(0, nil) } return nil } if tv.tut.Config.Input.MainCompose.Match(event.Key(), event.Rune()) { tv.InitPost(nil, nil) return nil } return tv.InputItem(event) } func (tv *TutView) InputMainViewContent(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { tv.Timeline.ScrollDown() return nil } if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { tv.Timeline.ScrollDown() return nil } if tv.tut.Config.Input.MainCompose.Match(event.Key(), event.Rune()) { tv.InitPost(nil, nil) return nil } return tv.InputItem(event) } func (tv *TutView) InputHelp(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.PrevFocus() return nil } return event } func (tv *TutView) InputViewItem(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.FocusMainNoHistory() return nil } return event } func (tv *TutView) InputItem(event *tcell.EventKey) *tcell.EventKey { fd := tv.GetCurrentFeed() ft := fd.Data.Type() item, err := tv.GetCurrentItem() if err != nil { return event } switch item.Type() { case api.StatusType: return tv.InputStatus(event, item, item.Raw().(*mastodon.Status), nil, fd.Data.Type()) case api.StatusHistoryType: return tv.InputStatusHistory(event, item, item.Raw().(*mastodon.StatusHistory), nil) case api.UserType, api.ProfileType: switch ft { case config.FollowRequests: return tv.InputUser(event, item.Raw().(*api.User), InputUserFollowRequest) case config.ListUsersAdd: return tv.InputUser(event, item.Raw().(*api.User), InputUserListAdd) case config.ListUsersIn: return tv.InputUser(event, item.Raw().(*api.User), InputUserListDelete) default: return tv.InputUser(event, item.Raw().(*api.User), InputUserNormal) } case api.NotificationType: nd := item.Raw().(*api.NotificationData) switch nd.Item.Type { case "follow": return tv.InputUser(event, nd.User.Raw().(*api.User), InputUserNormal) case "favourite": user := nd.User.Raw().(*api.User) return tv.InputStatus(event, nd.Status, nd.Status.Raw().(*mastodon.Status), user.Data, config.Notifications) case "reblog": user := nd.User.Raw().(*api.User) return tv.InputStatus(event, nd.Status, nd.Status.Raw().(*mastodon.Status), user.Data, config.Notifications) case "mention": return tv.InputStatus(event, nd.Status, nd.Status.Raw().(*mastodon.Status), nil, config.Notifications) case "update": return tv.InputStatus(event, nd.Status, nd.Status.Raw().(*mastodon.Status), nil, config.Notifications) case "status": return tv.InputStatus(event, nd.Status, nd.Status.Raw().(*mastodon.Status), nil, config.Notifications) case "poll": return tv.InputStatus(event, nd.Status, nd.Status.Raw().(*mastodon.Status), nil, config.Notifications) case "follow_request": return tv.InputUser(event, nd.User.Raw().(*api.User), InputUserFollowRequest) } case api.ListsType: ld := item.Raw().(*mastodon.List) return tv.InputList(event, ld) case api.TagType: tag := item.Raw().(*mastodon.Tag) return tv.InputTag(event, tag) } return event } func (tv *TutView) InputStatus(event *tcell.EventKey, item api.Item, status *mastodon.Status, nAcc *mastodon.Account, fd config.FeedType) *tcell.EventKey { sr := util.StatusOrReblog(status) hasMedia := len(sr.MediaAttachments) > 0 hasPoll := sr.Poll != nil hasSpoiler := sr.Sensitive isMine := sr.Account.ID == tv.tut.Client.Me.ID boosted, favorited, bookmarked := false, false, false if sr.Reblogged != nil { boosted = sr.Reblogged.(bool) } if sr.Favourited != nil { favorited = sr.Favourited.(bool) } if sr.Bookmarked != nil { bookmarked = sr.Bookmarked.(bool) } if tv.tut.Config.Input.StatusAvatar.Match(event.Key(), event.Rune()) { if nAcc != nil { openAvatar(tv, *nAcc) } else { openAvatar(tv, sr.Account) } return nil } if tv.tut.Config.Input.StatusBoost.Match(event.Key(), event.Rune()) { txt := "boost" if boosted { txt = "unboost" } tv.ModalView.Run( fmt.Sprintf("Do you want to %s this toot?", txt), func() { ns, err := tv.tut.Client.BoostToggle(status) if err != nil { tv.ShowError( fmt.Sprintf("Couldn't boost toot. Error: %v\n", err), ) return } *status = *ns tv.RedrawControls() }) return nil } if tv.tut.Config.Input.StatusDelete.Match(event.Key(), event.Rune()) { if !isMine { return nil } tv.ModalView.Run("Do you want to delete this toot?", func() { err := tv.tut.Client.DeleteStatus(sr) if err != nil { tv.ShowError( fmt.Sprintf("Couldn't delete toot. Error: %v\n", err), ) return } status.Card = nil status.Sensitive = false status.SpoilerText = "" status.Favourited = false status.MediaAttachments = nil status.Reblogged = false status.Content = "Deleted" tv.RedrawContent() }) return nil } if tv.tut.Config.Input.StatusEdit.Match(event.Key(), event.Rune()) { tv.EditCommand() return nil } if tv.tut.Config.Input.StatusFavorite.Match(event.Key(), event.Rune()) { txt := "favorite" if favorited { txt = "unfavorite" } tv.ModalView.Run(fmt.Sprintf("Do you want to %s this toot?", txt), func() { ns, err := tv.tut.Client.FavoriteToogle(status) if err != nil { tv.ShowError( fmt.Sprintf("Couldn't favorite toot. Error: %v\n", err), ) return } *status = *ns tv.RedrawControls() }) return nil } if tv.tut.Config.Input.StatusMedia.Match(event.Key(), event.Rune()) { if hasMedia { openMedia(tv, sr) } return nil } if tv.tut.Config.Input.StatusLinks.Match(event.Key(), event.Rune()) { tv.SetPage(LinkFocus) return nil } if tv.tut.Config.Input.StatusPoll.Match(event.Key(), event.Rune()) { if !hasPoll { return nil } tv.VoteView.SetPoll(sr.Poll) tv.SetPage(VoteFocus) return nil } if tv.tut.Config.Input.StatusReply.Match(event.Key(), event.Rune()) { tv.InitPost(status, nil) return nil } if tv.tut.Config.Input.StatusBookmark.Match(event.Key(), event.Rune()) { txt := "save" if bookmarked { txt = "unsave" } tv.ModalView.Run(fmt.Sprintf("Do you want to %s this toot?", txt), func() { ns, err := tv.tut.Client.BookmarkToogle(status) if err != nil { tv.ShowError( fmt.Sprintf("Couldn't bookmark toot. Error: %v\n", err), ) return } *status = *ns tv.RedrawControls() }) return nil } if tv.tut.Config.Input.StatusThread.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewThreadFeed(tv, item, config.NewTimeline(config.Timeline{ FeedType: config.Thread, })), false) return nil } if tv.tut.Config.Input.StatusUser.Match(event.Key(), event.Rune()) { id := sr.Account.ID if nAcc != nil { id = nAcc.ID } user, err := tv.tut.Client.GetUserByID(id) if err != nil { return nil } tv.Timeline.AddFeed(NewUserFeed(tv, user, config.NewTimeline(config.Timeline{ FeedType: config.User, })), false) return nil } if tv.tut.Config.Input.StatusViewFocus.Match(event.Key(), event.Rune()) { tv.SetPage(ViewFocus) return nil } if tv.tut.Config.Input.StatusYank.Match(event.Key(), event.Rune()) { copyToClipboard(sr.URL) return nil } if tv.tut.Config.Input.StatusToggleCW.Match(event.Key(), event.Rune()) { filtered, _, _, forceView := item.Filtered(fd) if filtered && !forceView { item.ForceViewFilter() tv.RedrawContent() return nil } if !hasSpoiler { return nil } if !item.ShowCW() { item.ToggleCW() tv.RedrawContent() } return nil } return event } func (tv *TutView) InputStatusHistory(event *tcell.EventKey, item api.Item, sr *mastodon.StatusHistory, nAcc *mastodon.Account) *tcell.EventKey { hasMedia := len(sr.MediaAttachments) > 0 hasSpoiler := sr.Sensitive status := &mastodon.Status{ Content: sr.Content, SpoilerText: sr.SpoilerText, Account: sr.Account, Sensitive: sr.Sensitive, CreatedAt: sr.CreatedAt, Emojis: sr.Emojis, MediaAttachments: sr.MediaAttachments, } if tv.tut.Config.Input.StatusAvatar.Match(event.Key(), event.Rune()) { if nAcc != nil { openAvatar(tv, *nAcc) } else { openAvatar(tv, sr.Account) } return nil } if tv.tut.Config.Input.StatusMedia.Match(event.Key(), event.Rune()) { if hasMedia { openMedia(tv, status) } return nil } if tv.tut.Config.Input.StatusLinks.Match(event.Key(), event.Rune()) { tv.SetPage(LinkFocus) return nil } if tv.tut.Config.Input.StatusViewFocus.Match(event.Key(), event.Rune()) { tv.SetPage(ViewFocus) return nil } if tv.tut.Config.Input.StatusToggleCW.Match(event.Key(), event.Rune()) { if !hasSpoiler { return nil } if !item.ShowCW() { item.ToggleCW() tv.RedrawContent() } return nil } return event } type InputUserType uint const ( InputUserNormal = iota InputUserFollowRequest InputUserListAdd InputUserListDelete ) func (tv *TutView) InputUser(event *tcell.EventKey, user *api.User, ut InputUserType) *tcell.EventKey { blocking := user.Relation.Blocking muting := user.Relation.Muting following := user.Relation.Following if ut == InputUserListAdd { if tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.ListUserAdd.Match(event.Key(), event.Rune()) { ad := user.AdditionalData switch ad.(type) { case *mastodon.List: l := user.AdditionalData.(*mastodon.List) err := tv.tut.Client.AddUserToList(user.Data, l) if err != nil { tv.ShowError(fmt.Sprintf("Couldn't add user to list. Error: %v", err)) } return nil default: return event } } return event } if ut == InputUserListDelete { if tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.ListUserDelete.Match(event.Key(), event.Rune()) { ad := user.AdditionalData switch ad.(type) { case *mastodon.List: l := user.AdditionalData.(*mastodon.List) err := tv.tut.Client.DeleteUserFromList(user.Data, l) if err != nil { tv.ShowError(fmt.Sprintf("Couldn't remove user from list. Error: %v", err)) } return nil default: return event } } return event } if ut == InputUserFollowRequest && tv.tut.Config.Input.UserFollowRequestDecide.Match(event.Key(), event.Rune()) { tv.ModalView.RunDecide("Do you want accept the follow request?", func() { err := tv.tut.Client.FollowRequestAccept(user.Data) if err != nil { tv.ShowError( fmt.Sprintf("Couldn't accept follow request. Error: %v\n", err), ) return } f := tv.GetCurrentFeed() f.Delete() tv.RedrawContent() }, func() { err := tv.tut.Client.FollowRequestDeny(user.Data) if err != nil { tv.ShowError( fmt.Sprintf("Couldn't deny follow request. Error: %v\n", err), ) return } f := tv.GetCurrentFeed() f.Delete() tv.RedrawContent() }) return nil } if tv.tut.Config.Input.UserAvatar.Match(event.Key(), event.Rune()) { openAvatar(tv, *user.Data) return nil } if tv.tut.Config.Input.UserBlock.Match(event.Key(), event.Rune()) { txt := "block" if blocking { txt = "unblock" } tv.ModalView.Run(fmt.Sprintf("Do you want to %s this user?", txt), func() { rel, err := tv.tut.Client.BlockToggle(user) if err != nil { tv.ShowError( fmt.Sprintf("Couldn't block user. Error: %v\n", err), ) return } user.Relation = rel tv.RedrawControls() }) return nil } if tv.tut.Config.Input.UserFollow.Match(event.Key(), event.Rune()) { txt := "follow" if following { txt = "unfollow" } tv.ModalView.Run(fmt.Sprintf("Do you want to %s this user?", txt), func() { rel, err := tv.tut.Client.FollowToggle(user) if err != nil { tv.ShowError( fmt.Sprintf("Couldn't follow user. Error: %v\n", err), ) return } user.Relation = rel tv.RedrawControls() }) return nil } if tv.tut.Config.Input.UserMute.Match(event.Key(), event.Rune()) { txt := "mute" if muting { txt = "unmute" } tv.ModalView.Run(fmt.Sprintf("Do you want to %s this user?", txt), func() { rel, err := tv.tut.Client.MuteToggle(user) if err != nil { tv.ShowError( fmt.Sprintf("Couldn't follow user. Error: %v\n", err), ) return } user.Relation = rel tv.RedrawControls() }) return nil } if tv.tut.Config.Input.UserLinks.Match(event.Key(), event.Rune()) { tv.SetPage(LinkFocus) return nil } if tv.tut.Config.Input.UserUser.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewUserFeed(tv, api.NewUserItem(user, true), config.NewTimeline(config.Timeline{ FeedType: config.User, })), false) return nil } if tv.tut.Config.Input.UserViewFocus.Match(event.Key(), event.Rune()) { tv.SetPage(ViewFocus) return nil } if tv.tut.Config.Input.UserYank.Match(event.Key(), event.Rune()) { copyToClipboard(user.Data.URL) return nil } if tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewUserFeed(tv, api.NewUserItem(user, true), config.NewTimeline(config.Timeline{ FeedType: config.User, })), false) return nil } return event } func (tv *TutView) InputList(event *tcell.EventKey, list *mastodon.List) *tcell.EventKey { if tv.tut.Config.Input.ListOpenFeed.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewListFeed(tv, list, config.NewTimeline(config.Timeline{ FeedType: config.List, })), false) return nil } if tv.tut.Config.Input.ListUserList.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewUsersInListFeed(tv, list, config.NewTimeline(config.Timeline{ FeedType: config.ListUsersIn, })), false) return nil } if tv.tut.Config.Input.ListUserAdd.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewUsersAddListFeed(tv, list, config.NewTimeline(config.Timeline{ FeedType: config.ListUsersAdd, })), false) return nil } return event } func (tv *TutView) InputTag(event *tcell.EventKey, tag *mastodon.Tag) *tcell.EventKey { if tv.tut.Config.Input.TagOpenFeed.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { tv.Timeline.AddFeed(NewTagFeed(tv, config.NewTimeline(config.Timeline{ FeedType: config.Tag, Subaction: tag.Name, })), false) return nil } if tv.tut.Config.Input.TagFollow.Match(event.Key(), event.Rune()) { txt := "follow" if tag.Following != nil && tag.Following == true { txt = "unfollow" } tv.ModalView.Run(fmt.Sprintf("Do you want to %s #%s?", txt, tag.Name), func() { nt, err := tv.tut.Client.TagToggleFollow(tag) if err != nil { tv.ShowError( fmt.Sprintf("Couldn't %s tag. Error: %v\n", txt, err), ) return } *tag = *nt tv.RedrawControls() }) return nil } return event } func (tv *TutView) InputLinkView(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { tv.LinkView.Next() return nil } if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { tv.LinkView.Prev() return nil } if tv.tut.Config.Input.LinkOpen.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { tv.LinkView.Open() return nil } if tv.tut.Config.Input.LinkYank.Match(event.Key(), event.Rune()) { tv.LinkView.Yank() return nil } if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.SetPage(MainFocus) return nil } for _, oc := range tv.tut.Config.OpenCustom.OpenCustoms { if oc.Key.Match(event.Key(), event.Rune()) { tv.LinkView.OpenCustom(oc) return nil } } return event } func (tv *TutView) InputComposeView(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.ComposeEditCW.Match(event.Key(), event.Rune()) { tv.ComposeView.EditSpoiler() return nil } if tv.tut.Config.Input.ComposeEditText.Match(event.Key(), event.Rune()) { tv.ComposeView.EditText() return nil } if tv.tut.Config.Input.ComposeIncludeQuote.Match(event.Key(), event.Rune()) { tv.ComposeView.IncludeQuote() return nil } if tv.tut.Config.Input.ComposeMediaFocus.Match(event.Key(), event.Rune()) { if tv.PollView.HasPoll() { tv.ShowError("Can't add media when you have a poll") return nil } tv.SetPage(MediaFocus) return nil } if tv.tut.Config.Input.ComposePoll.Match(event.Key(), event.Rune()) { if tv.ComposeView.HasMedia() { tv.ShowError("Can't add poll when you have added media") return nil } tv.SetPage(PollFocus) return nil } if tv.tut.Config.Input.ComposePost.Match(event.Key(), event.Rune()) { tv.ComposeView.Post() return nil } if tv.tut.Config.Input.ComposeToggleContentWarning.Match(event.Key(), event.Rune()) { tv.ComposeView.ToggleCW() return nil } if tv.tut.Config.Input.ComposeVisibility.Match(event.Key(), event.Rune()) { tv.ComposeView.FocusVisibility() return nil } if tv.tut.Config.Input.ComposeLanguage.Match(event.Key(), event.Rune()) { tv.ComposeView.FocusLang() return nil } if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.ModalView.Run( "Do you want exit the compose view?", func() { tv.FocusMainNoHistory() }) return nil } return event } func (tv *TutView) InputMedia(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { tv.ComposeView.media.Next() return nil } if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { tv.ComposeView.media.Prev() return nil } if tv.tut.Config.Input.MediaDelete.Match(event.Key(), event.Rune()) { tv.ComposeView.media.Delete() return nil } if tv.tut.Config.Input.MediaEditDesc.Match(event.Key(), event.Rune()) { tv.ComposeView.media.EditDesc() return nil } if tv.tut.Config.Input.MediaAdd.Match(event.Key(), event.Rune()) { tv.SetPage(MediaAddFocus) tv.ComposeView.media.SetFocus(false) return nil } if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.SetPage(ComposeFocus) return nil } return event } func (tv *TutView) InputMediaAdd(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyRune { tv.ComposeView.input.AddRune(event.Rune()) return nil } switch event.Key() { case tcell.KeyTAB: tv.ComposeView.input.AutocompleteTab() return nil case tcell.KeyDown: tv.ComposeView.input.AutocompleteNext() return nil case tcell.KeyBacktab, tcell.KeyUp: tv.ComposeView.input.AutocompletePrev() return nil case tcell.KeyEnter: tv.ComposeView.input.CheckDone() return nil case tcell.KeyEsc: tv.SetPage(MediaFocus) tv.ComposeView.media.SetFocus(true) return nil } return event } func (tv *TutView) InputPollView(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.PollAdd.Match(event.Key(), event.Rune()) { tv.PollView.Add() return nil } if tv.tut.Config.Input.PollEdit.Match(event.Key(), event.Rune()) { tv.PollView.Edit() return nil } if tv.tut.Config.Input.PollDelete.Match(event.Key(), event.Rune()) { tv.PollView.Delete() return nil } if tv.tut.Config.Input.PollMultiToggle.Match(event.Key(), event.Rune()) { tv.PollView.ToggleMultiple() return nil } if tv.tut.Config.Input.PollExpiration.Match(event.Key(), event.Rune()) { tv.PollView.FocusExpiration() return nil } if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { tv.PollView.Next() return nil } if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { tv.PollView.Prev() return nil } if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.SetPage(ComposeFocus) return nil } return event } func (tv *TutView) InputVote(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { tv.VoteView.Next() return nil } if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { tv.VoteView.Prev() return nil } if tv.tut.Config.Input.VoteVote.Match(event.Key(), event.Rune()) { tv.VoteView.Vote() return nil } if tv.tut.Config.Input.VoteSelect.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalEnter.Match(event.Key(), event.Rune()) { tv.VoteView.ToggleSelect() return nil } if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.FocusMainNoHistory() return nil } return event } func (tv *TutView) InputPreference(event *tcell.EventKey) *tcell.EventKey { if tv.PreferenceView.HasFieldFocus() { return tv.InputPreferenceFields(event) } if tv.tut.Config.Input.PreferenceFields.Match(event.Key(), event.Rune()) { tv.PreferenceView.FieldFocus() return nil } if tv.tut.Config.Input.PreferenceName.Match(event.Key(), event.Rune()) { tv.PreferenceView.EditDisplayname() return nil } if tv.tut.Config.Input.PreferenceVisibility.Match(event.Key(), event.Rune()) { tv.PreferenceView.FocusVisibility() return nil } if tv.tut.Config.Input.PreferenceBio.Match(event.Key(), event.Rune()) { tv.PreferenceView.EditBio() return nil } if tv.tut.Config.Input.PreferenceSave.Match(event.Key(), event.Rune()) { tv.PreferenceView.Save() return nil } if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.ModalView.Run( "Do you want exit the preference view?", func() { tv.FocusMainNoHistory() }) return nil } return event } func (tv *TutView) InputPreferenceFields(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.GlobalUp.Match(event.Key(), event.Rune()) { tv.PreferenceView.PrevField() return nil } if tv.tut.Config.Input.GlobalDown.Match(event.Key(), event.Rune()) { tv.PreferenceView.NextField() return nil } if tv.tut.Config.Input.PreferenceFieldsAdd.Match(event.Key(), event.Rune()) { tv.PreferenceView.AddField() return nil } if tv.tut.Config.Input.PreferenceFieldsEdit.Match(event.Key(), event.Rune()) { tv.PreferenceView.EditField() return nil } if tv.tut.Config.Input.PreferenceFieldsDelete.Match(event.Key(), event.Rune()) { tv.PreferenceView.DeleteField() return nil } if tv.tut.Config.Input.GlobalBack.Match(event.Key(), event.Rune()) || tv.tut.Config.Input.GlobalExit.Match(event.Key(), event.Rune()) { tv.PreferenceView.MainFocus() return nil } return event } func (tv *TutView) InputCmdView(event *tcell.EventKey) *tcell.EventKey { switch event.Key() { case tcell.KeyEnter: tv.Shared.Bottom.Cmd.DoneFunc(tcell.KeyEnter) case tcell.KeyEsc: tv.Shared.Bottom.Cmd.Back() tv.Shared.Bottom.Cmd.View.Autocomplete() return nil } return event } func (tv *TutView) InputEditorView(event *tcell.EventKey) *tcell.EventKey { if tv.tut.Config.Input.GlobalBack.Match(event.Key(), rune(-1)) { tv.EditorView.ExitTextAreaInput() return nil } switch key := event.Key(); key { case tcell.KeyCtrlQ: return nil case tcell.KeyCtrlC: return tcell.NewEventKey(tcell.KeyCtrlQ, rune(0), tcell.ModNone) } return event } func (tv *TutView) MouseInput(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) { if event == nil { return nil, action } switch action { case tview.MouseLeftUp, tview.MouseMiddleUp, tview.MouseRightUp: return event, action } /* Switch accounts */ x, y := event.Position() if tv.MainView.accView.InRect(x, y) { return event, action } switch tv.PageFocus { case ViewFocus, MainFocus: return tv.MouseInputMainView(event, action) case LoginFocus: tv.MouseInputLoginView(event, action) case LinkFocus: return tv.MouseInputLinkView(event, action) case MediaFocus: return tv.MouseInputMediaView(event, action) case VoteFocus: return tv.MouseInputVoteView(event, action) case ModalFocus: tv.MouseInputModalView(event, action) case PollFocus: return tv.MouseInputPollView(event, action) case HelpFocus: return tv.MouseInputHelpView(event, action) case ComposeFocus: return tv.MouseInputComposeView(event, action) case PreferenceFocus: return tv.MouseInputPreferenceView(event, action) } return nil, action } func (tv *TutView) feedListMouse(list *tview.List, i int, event *tcell.EventMouse, action tview.MouseAction) { tv.SetPage(MainFocus) tv.FocusFeed(i, nil) mh := list.MouseHandler() if mh == nil { return } lastIndex := list.GetCurrentItem() mh(action, event, func(p tview.Primitive) {}) newIndex := list.GetCurrentItem() if lastIndex != newIndex { tv.Timeline.SetItemFeedIndex(newIndex) } } var scrollSleepTime time.Duration = 150 type scrollSleep struct { mux sync.Mutex last time.Time next func() prev func() } func NewScrollSleep(next func(), prev func()) *scrollSleep { return &scrollSleep{ next: next, prev: prev, } } func (sc *scrollSleep) Action(list *tview.List, action tview.MouseAction) { mh := list.MouseHandler() if mh == nil { return } lock := sc.mux.TryLock() if !lock { return } if time.Since(sc.last) < (scrollSleepTime * time.Millisecond) { sc.mux.Unlock() return } if action == tview.MouseScrollDown { sc.next() } if action == tview.MouseScrollUp { sc.prev() } sc.last = time.Now() sc.mux.Unlock() } func (tv *TutView) MouseInputMainView(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) { x, y := event.Position() switch action { case tview.MouseScrollDown, tview.MouseScrollUp: f := tv.GetCurrentFeed() if f.Content.Main.InRect(x, y) { if action == tview.MouseScrollDown { tv.Timeline.ScrollDown() return nil, action } if action == tview.MouseScrollUp { tv.Timeline.ScrollUp() return nil, action } } for _, tl := range tv.Timeline.Feeds { fl := tl.GetFeedList() if fl.Text.InRect(x, y) { tv.Timeline.scrollSleep.Action(fl.Text, action) return nil, action } if fl.Symbol.InRect(x, y) { tv.Timeline.scrollSleep.Action(fl.Symbol, action) return nil, action } } case tview.MouseLeftClick: f := tv.GetCurrentFeed() if f.Content.Main.InRect(x, y) { tv.SetPage(ViewFocus) return nil, action } if f.Content.Controls.InRect(x, y) { return event, action } for i, tl := range tv.Timeline.Feeds { fl := tl.GetFeedList() if fl.Text.InRect(x, y) { tv.feedListMouse(fl.Text, i, event, action) return nil, action } if fl.Symbol.InRect(x, y) { tv.feedListMouse(fl.Symbol, i, event, action) return nil, action } } } return nil, action } func (tv *TutView) MouseInputLoginView(event *tcell.EventMouse, action tview.MouseAction) { x, y := event.Position() switch action { case tview.MouseLeftClick: list := tv.LoginView.list if !list.InRect(x, y) { return } mh := list.MouseHandler() if mh == nil { return } mh(action, event, func(p tview.Primitive) {}) tv.LoginView.Selected() case tview.MouseScrollDown, tview.MouseScrollUp: tv.LoginView.scrollSleep.Action(tv.LoginView.list, action) } } func (tv *TutView) MouseInputLinkView(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) { x, y := event.Position() switch action { case tview.MouseLeftClick: if tv.LinkView.controls.InRect(x, y) { return event, action } list := tv.LinkView.list if !list.InRect(x, y) { return nil, action } mh := list.MouseHandler() if mh == nil { return nil, action } mh(action, event, func(p tview.Primitive) {}) tv.LinkView.Open() case tview.MouseScrollDown, tview.MouseScrollUp: tv.LinkView.scrollSleep.Action(tv.LinkView.list, action) } return nil, action } func (tv *TutView) MouseInputMediaView(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) { x, y := event.Position() switch action { case tview.MouseLeftClick: if tv.ComposeView.controls.InRect(x, y) { return event, action } list := tv.ComposeView.media.list if !list.InRect(x, y) { return nil, action } mh := list.MouseHandler() if mh == nil { return nil, action } mh(action, event, func(p tview.Primitive) {}) case tview.MouseScrollDown, tview.MouseScrollUp: tv.ComposeView.media.scrollSleep.Action(tv.ComposeView.media.list, action) } return nil, action } func (tv *TutView) MouseInputVoteView(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) { x, y := event.Position() switch action { case tview.MouseLeftClick: if tv.VoteView.controls.InRect(x, y) { return event, action } list := tv.VoteView.list if !list.InRect(x, y) { return nil, action } mh := list.MouseHandler() if mh == nil { return nil, action } mh(action, event, func(p tview.Primitive) {}) tv.VoteView.ToggleSelect() case tview.MouseScrollDown, tview.MouseScrollUp: tv.VoteView.scrollSleep.Action(tv.VoteView.list, action) } return nil, action } func (tv *TutView) MouseInputModalView(event *tcell.EventMouse, action tview.MouseAction) { switch action { case tview.MouseLeftClick: modal := tv.ModalView.View mh := modal.MouseHandler() if mh == nil { return } mh(action, event, func(p tview.Primitive) {}) } } func (tv *TutView) MouseInputPollView(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) { x, y := event.Position() switch action { case tview.MouseLeftClick: if tv.PollView.controls.InRect(x, y) { return event, action } list := tv.PollView.list if !list.InRect(x, y) { return nil, action } mh := list.MouseHandler() if mh == nil { return nil, action } mh(action, event, func(p tview.Primitive) {}) case tview.MouseScrollDown, tview.MouseScrollUp: tv.PollView.scrollSleep.Action(tv.PollView.list, action) } return nil, action } func (tv *TutView) MouseInputHelpView(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) { x, y := event.Position() switch action { case tview.MouseLeftClick: if tv.HelpView.controls.InRect(x, y) { return event, action } } return nil, action } func (tv *TutView) MouseInputPreferenceView(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) { x, y := event.Position() switch action { case tview.MouseLeftClick: if tv.PreferenceView.controls.InRect(x, y) { return event, action } } return nil, action } func (tv *TutView) MouseInputComposeView(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) { x, y := event.Position() switch action { case tview.MouseLeftClick: if tv.ComposeView.controls.InRect(x, y) { return event, action } } return nil, action }