14 changed files with 1523 additions and 653 deletions
@ -0,0 +1,948 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/gdamore/tcell" |
||||
"github.com/mattn/go-mastodon" |
||||
"github.com/rivo/tview" |
||||
) |
||||
|
||||
type FeedType uint |
||||
|
||||
const ( |
||||
TimelineFeed FeedType = iota |
||||
ThreadFeed |
||||
UserFeed |
||||
NotificationFeed |
||||
) |
||||
|
||||
type Feed interface { |
||||
GetFeedList() <-chan string |
||||
LoadNewer() int |
||||
LoadOlder() int |
||||
DrawList() |
||||
DrawToot() |
||||
FeedType() FeedType |
||||
GetSavedIndex() int |
||||
Input(event *tcell.EventKey) |
||||
} |
||||
|
||||
func showTootOptions(app *App, status *mastodon.Status, showSensitive bool) (string, string) { |
||||
var line string |
||||
width := app.UI.StatusView.GetTextWidth() |
||||
for i := 0; i < width; i++ { |
||||
line += "-" |
||||
} |
||||
line += "\n" |
||||
|
||||
shouldDisplay := !status.Sensitive || showSensitive |
||||
|
||||
var stripped string |
||||
var urls []URL |
||||
var u []URL |
||||
if status.Sensitive && !showSensitive { |
||||
stripped, u = cleanTootHTML(status.SpoilerText) |
||||
urls = append(urls, u...) |
||||
stripped += "\n" + line |
||||
stripped += "Press [s] to show hidden text" |
||||
|
||||
} else { |
||||
stripped, u = cleanTootHTML(status.Content) |
||||
urls = append(urls, u...) |
||||
|
||||
if status.Sensitive { |
||||
sens, u := cleanTootHTML(status.SpoilerText) |
||||
urls = append(urls, u...) |
||||
stripped = sens + "\n\n" + stripped |
||||
} |
||||
} |
||||
app.UI.LinkOverlay.SetURLs(urls) |
||||
|
||||
subtleColor := fmt.Sprintf("[#%x]", app.Config.Style.Subtle.Hex()) |
||||
special1 := fmt.Sprintf("[#%x]", app.Config.Style.TextSpecial1.Hex()) |
||||
special2 := fmt.Sprintf("[#%x]", app.Config.Style.TextSpecial2.Hex()) |
||||
var head string |
||||
if status.Reblog != nil { |
||||
if status.Account.DisplayName != "" { |
||||
head += fmt.Sprintf(subtleColor+"%s (%s)\n", status.Account.DisplayName, status.Account.Acct) |
||||
} else { |
||||
head += fmt.Sprintf(subtleColor+"%s\n", status.Account.Acct) |
||||
} |
||||
head += subtleColor + "Boosted\n" |
||||
head += subtleColor + line |
||||
status = status.Reblog |
||||
} |
||||
|
||||
if status.Account.DisplayName != "" { |
||||
head += fmt.Sprintf(special2+"%s\n", status.Account.DisplayName) |
||||
} |
||||
head += fmt.Sprintf(special1+"%s\n\n", status.Account.Acct) |
||||
output := head |
||||
content := tview.Escape(stripped) |
||||
if content != "" { |
||||
output += content + "\n\n" |
||||
} |
||||
|
||||
var poll string |
||||
if status.Poll != nil { |
||||
poll += subtleColor + "Poll\n" |
||||
poll += subtleColor + line |
||||
poll += fmt.Sprintf("Number of votes: %d\n\n", status.Poll.VotesCount) |
||||
votes := float64(status.Poll.VotesCount) |
||||
for _, o := range status.Poll.Options { |
||||
res := 0.0 |
||||
if votes != 0 { |
||||
res = float64(o.VotesCount) / votes * 100 |
||||
} |
||||
poll += fmt.Sprintf("%s - %.2f%% (%d)\n", tview.Escape(o.Title), res, o.VotesCount) |
||||
} |
||||
poll += "\n" |
||||
} |
||||
|
||||
var media string |
||||
for _, att := range status.MediaAttachments { |
||||
media += subtleColor + line |
||||
media += fmt.Sprintf(subtleColor+"Attached %s\n", att.Type) |
||||
media += fmt.Sprintf("%s\n", att.URL) |
||||
} |
||||
if len(status.MediaAttachments) > 0 { |
||||
media += "\n" |
||||
} |
||||
|
||||
var card string |
||||
if status.Card != nil { |
||||
card += subtleColor + "Card type: " + status.Card.Type + "\n" |
||||
card += subtleColor + line |
||||
if status.Card.Title != "" { |
||||
card += status.Card.Title + "\n\n" |
||||
} |
||||
desc := strings.TrimSpace(status.Card.Description) |
||||
if desc != "" { |
||||
card += desc + "\n\n" |
||||
} |
||||
card += status.Card.URL |
||||
} |
||||
|
||||
if shouldDisplay { |
||||
output += poll + media + card |
||||
} |
||||
|
||||
app.UI.StatusView.ScrollToBeginning() |
||||
var info []string |
||||
if status.Favourited == true { |
||||
info = append(info, ColorKey(app.Config.Style, "Un", "F", "avorite")) |
||||
} else { |
||||
info = append(info, ColorKey(app.Config.Style, "", "F", "avorite")) |
||||
} |
||||
if status.Reblogged == true { |
||||
info = append(info, ColorKey(app.Config.Style, "Un", "B", "boost")) |
||||
} else { |
||||
info = append(info, ColorKey(app.Config.Style, "", "B", "boost")) |
||||
} |
||||
info = append(info, ColorKey(app.Config.Style, "", "T", "hread")) |
||||
info = append(info, ColorKey(app.Config.Style, "", "R", "eply")) |
||||
info = append(info, ColorKey(app.Config.Style, "", "V", "iew")) |
||||
info = append(info, ColorKey(app.Config.Style, "", "U", "ser")) |
||||
if len(status.MediaAttachments) > 0 { |
||||
info = append(info, ColorKey(app.Config.Style, "", "M", "edia")) |
||||
} |
||||
if len(urls) > 0 { |
||||
info = append(info, ColorKey(app.Config.Style, "", "O", "pen")) |
||||
} |
||||
|
||||
if status.Account.ID == app.Me.ID { |
||||
info = append(info, ColorKey(app.Config.Style, "", "D", "elete")) |
||||
} |
||||
|
||||
controls := strings.Join(info, " ") |
||||
return output, controls |
||||
} |
||||
|
||||
func drawStatusList(statuses []*mastodon.Status) <-chan string { |
||||
ch := make(chan string) |
||||
go func() { |
||||
today := time.Now() |
||||
ty, tm, td := today.Date() |
||||
for _, s := range statuses { |
||||
|
||||
sLocal := s.CreatedAt.Local() |
||||
sy, sm, sd := sLocal.Date() |
||||
format := "2006-01-02 15:04" |
||||
if ty == sy && tm == sm && td == sd { |
||||
format = "15:04" |
||||
} |
||||
content := fmt.Sprintf("%s %s", sLocal.Format(format), s.Account.Acct) |
||||
ch <- content |
||||
} |
||||
close(ch) |
||||
}() |
||||
return ch |
||||
} |
||||
|
||||
func NewTimeline(app *App, tl TimelineType) *Timeline { |
||||
t := &Timeline{ |
||||
app: app, |
||||
timelineType: tl, |
||||
} |
||||
t.statuses, _ = t.app.API.GetStatuses(t.timelineType) |
||||
return t |
||||
} |
||||
|
||||
type Timeline struct { |
||||
app *App |
||||
timelineType TimelineType |
||||
statuses []*mastodon.Status |
||||
index int |
||||
showSpoiler bool |
||||
} |
||||
|
||||
func (t *Timeline) FeedType() FeedType { |
||||
return TimelineFeed |
||||
} |
||||
|
||||
func (t *Timeline) GetCurrentStatus() *mastodon.Status { |
||||
index := t.app.UI.StatusView.GetCurrentItem() |
||||
if index >= len(t.statuses) { |
||||
return nil |
||||
} |
||||
return t.statuses[t.app.UI.StatusView.GetCurrentItem()] |
||||
} |
||||
|
||||
func (t *Timeline) GetFeedList() <-chan string { |
||||
return drawStatusList(t.statuses) |
||||
} |
||||
|
||||
func (t *Timeline) LoadNewer() int { |
||||
var statuses []*mastodon.Status |
||||
var err error |
||||
if len(t.statuses) == 0 { |
||||
statuses, err = t.app.API.GetStatuses(t.timelineType) |
||||
} else { |
||||
statuses, _, err = t.app.API.GetStatusesNewer(t.timelineType, t.statuses[0]) |
||||
} |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
if len(statuses) == 0 { |
||||
return 0 |
||||
} |
||||
old := t.statuses |
||||
t.statuses = append(statuses, old...) |
||||
return len(statuses) |
||||
} |
||||
|
||||
func (t *Timeline) LoadOlder() int { |
||||
var statuses []*mastodon.Status |
||||
var err error |
||||
if len(t.statuses) == 0 { |
||||
statuses, err = t.app.API.GetStatuses(t.timelineType) |
||||
} else { |
||||
statuses, _, err = t.app.API.GetStatusesOlder(t.timelineType, t.statuses[len(t.statuses)-1]) |
||||
} |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
if len(statuses) == 0 { |
||||
return 0 |
||||
} |
||||
t.statuses = append(t.statuses, statuses...) |
||||
return len(statuses) |
||||
} |
||||
|
||||
func (t *Timeline) DrawList() { |
||||
t.app.UI.StatusView.SetList(t.GetFeedList()) |
||||
} |
||||
|
||||
func (t *Timeline) DrawToot() { |
||||
if len(t.statuses) == 0 { |
||||
t.app.UI.StatusView.SetText("") |
||||
t.app.UI.StatusView.SetControls("") |
||||
return |
||||
} |
||||
t.index = t.app.UI.StatusView.GetCurrentItem() |
||||
text, controls := showTootOptions(t.app, t.statuses[t.index], t.showSpoiler) |
||||
t.showSpoiler = false |
||||
t.app.UI.StatusView.SetText(text) |
||||
t.app.UI.StatusView.SetControls(controls) |
||||
} |
||||
|
||||
func (t *Timeline) redrawControls() { |
||||
status := t.GetCurrentStatus() |
||||
if status == nil { |
||||
return |
||||
} |
||||
_, controls := showTootOptions(t.app, status, t.showSpoiler) |
||||
t.app.UI.StatusView.SetControls(controls) |
||||
} |
||||
|
||||
func (t *Timeline) GetSavedIndex() int { |
||||
return t.index |
||||
} |
||||
|
||||
func (t *Timeline) Input(event *tcell.EventKey) { |
||||
status := t.GetCurrentStatus() |
||||
if status == nil { |
||||
return |
||||
} |
||||
if event.Key() == tcell.KeyRune { |
||||
switch event.Rune() { |
||||
case 't', 'T': |
||||
t.app.UI.StatusView.AddFeed( |
||||
NewThread(t.app, status), |
||||
) |
||||
case 'u', 'U': |
||||
t.app.UI.StatusView.AddFeed( |
||||
NewUser(t.app, status.Account), |
||||
) |
||||
case 's', 'S': |
||||
t.showSpoiler = true |
||||
t.DrawToot() |
||||
case 'c', 'C': |
||||
t.app.UI.NewToot() |
||||
case 'o', 'O': |
||||
t.app.UI.ShowLinks() |
||||
case 'r', 'R': |
||||
t.app.UI.Reply(status) |
||||
case 'm', 'M': |
||||
t.app.UI.OpenMedia(status) |
||||
case 'f', 'F': |
||||
index := t.app.UI.StatusView.GetCurrentItem() |
||||
newStatus, err := t.app.API.FavoriteToogle(status) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
t.statuses[index] = newStatus |
||||
t.redrawControls() |
||||
|
||||
case 'b', 'B': |
||||
index := t.app.UI.StatusView.GetCurrentItem() |
||||
newStatus, err := t.app.API.BoostToggle(status) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
t.statuses[index] = newStatus |
||||
t.redrawControls() |
||||
case 'd', 'D': |
||||
t.app.API.DeleteStatus(status) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func NewThread(app *App, s *mastodon.Status) *Thread { |
||||
t := &Thread{ |
||||
app: app, |
||||
} |
||||
statuses, index, err := t.app.API.GetThread(s) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
t.statuses = statuses |
||||
t.status = s |
||||
t.index = index |
||||
return t |
||||
} |
||||
|
||||
type Thread struct { |
||||
app *App |
||||
statuses []*mastodon.Status |
||||
status *mastodon.Status |
||||
index int |
||||
showSpoiler bool |
||||
} |
||||
|
||||
func (t *Thread) FeedType() FeedType { |
||||
return ThreadFeed |
||||
} |
||||
|
||||
func (t *Thread) GetCurrentStatus() *mastodon.Status { |
||||
index := t.app.UI.StatusView.GetCurrentItem() |
||||
if index >= len(t.statuses) { |
||||
return nil |
||||
} |
||||
return t.statuses[t.app.UI.StatusView.GetCurrentItem()] |
||||
} |
||||
|
||||
func (t *Thread) GetFeedList() <-chan string { |
||||
return drawStatusList(t.statuses) |
||||
} |
||||
|
||||
func (t *Thread) LoadNewer() int { |
||||
return 0 |
||||
} |
||||
|
||||
func (t *Thread) LoadOlder() int { |
||||
return 0 |
||||
} |
||||
|
||||
func (t *Thread) DrawList() { |
||||
t.app.UI.StatusView.SetList(t.GetFeedList()) |
||||
} |
||||
|
||||
func (t *Thread) DrawToot() { |
||||
status := t.GetCurrentStatus() |
||||
if status == nil { |
||||
t.app.UI.StatusView.SetText("") |
||||
t.app.UI.StatusView.SetControls("") |
||||
return |
||||
} |
||||
t.index = t.app.UI.StatusView.GetCurrentItem() |
||||
text, controls := showTootOptions(t.app, status, t.showSpoiler) |
||||
t.showSpoiler = false |
||||
t.app.UI.StatusView.SetText(text) |
||||
t.app.UI.StatusView.SetControls(controls) |
||||
} |
||||
|
||||
func (t *Thread) redrawControls() { |
||||
status := t.GetCurrentStatus() |
||||
if status == nil { |
||||
t.app.UI.StatusView.SetText("") |
||||
t.app.UI.StatusView.SetControls("") |
||||
return |
||||
} |
||||
_, controls := showTootOptions(t.app, status, t.showSpoiler) |
||||
t.app.UI.StatusView.SetControls(controls) |
||||
} |
||||
|
||||
func (t *Thread) GetSavedIndex() int { |
||||
return t.index |
||||
} |
||||
|
||||
func (t *Thread) Input(event *tcell.EventKey) { |
||||
status := t.GetCurrentStatus() |
||||
if status == nil { |
||||
return |
||||
} |
||||
if event.Key() == tcell.KeyRune { |
||||
switch event.Rune() { |
||||
case 't', 'T': |
||||
if t.status.ID != status.ID { |
||||
t.app.UI.StatusView.AddFeed( |
||||
NewThread(t.app, status), |
||||
) |
||||
} |
||||
case 'u', 'U': |
||||
t.app.UI.StatusView.AddFeed( |
||||
NewUser(t.app, status.Account), |
||||
) |
||||
case 's', 'S': |
||||
t.showSpoiler = true |
||||
t.DrawToot() |
||||
case 'c', 'C': |
||||
t.app.UI.NewToot() |
||||
case 'o', 'O': |
||||
t.app.UI.ShowLinks() |
||||
case 'r', 'R': |
||||
t.app.UI.Reply(status) |
||||
case 'm', 'M': |
||||
t.app.UI.OpenMedia(status) |
||||
case 'f', 'F': |
||||
index := t.app.UI.StatusView.GetCurrentItem() |
||||
newStatus, err := t.app.API.FavoriteToogle(status) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
t.statuses[index] = newStatus |
||||
t.redrawControls() |
||||
|
||||
case 'b', 'B': |
||||
index := t.app.UI.StatusView.GetCurrentItem() |
||||
newStatus, err := t.app.API.BoostToggle(status) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
t.statuses[index] = newStatus |
||||
t.redrawControls() |
||||
case 'd', 'D': |
||||
t.app.API.DeleteStatus(status) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func NewUser(app *App, a mastodon.Account) *User { |
||||
u := &User{ |
||||
app: app, |
||||
} |
||||
statuses, err := app.API.GetUserStatuses(a) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
u.statuses = statuses |
||||
relation, err := app.API.UserRelation(a) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
u.relation = relation |
||||
u.user = a |
||||
return u |
||||
} |
||||
|
||||
type User struct { |
||||
app *App |
||||
statuses []*mastodon.Status |
||||
user mastodon.Account |
||||
relation *mastodon.Relationship |
||||
index int |
||||
showSpoiler bool |
||||
} |
||||
|
||||
func (u *User) FeedType() FeedType { |
||||
return UserFeed |
||||
} |
||||
|
||||
func (u *User) GetCurrentStatus() *mastodon.Status { |
||||
index := u.app.UI.app.UI.StatusView.GetCurrentItem() |
||||
if index > 0 && index-1 >= len(u.statuses) { |
||||
return nil |
||||
} |
||||
return u.statuses[index-1] |
||||
} |
||||
|
||||
func (u *User) GetFeedList() <-chan string { |
||||
ch := make(chan string) |
||||
go func() { |
||||
ch <- "Profile" |
||||
for s := range drawStatusList(u.statuses) { |
||||
ch <- s |
||||
} |
||||
close(ch) |
||||
}() |
||||
return ch |
||||
} |
||||
|
||||
func (u *User) LoadNewer() int { |
||||
var statuses []*mastodon.Status |
||||
var err error |
||||
if len(u.statuses) == 0 { |
||||
statuses, err = u.app.API.GetUserStatuses(u.user) |
||||
} else { |
||||
statuses, _, err = u.app.API.GetUserStatusesNewer(u.user, u.statuses[0]) |
||||
} |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
if len(statuses) == 0 { |
||||
return 0 |
||||
} |
||||
old := u.statuses |
||||
u.statuses = append(statuses, old...) |
||||
return len(statuses) |
||||
} |
||||
|
||||
func (u *User) LoadOlder() int { |
||||
var statuses []*mastodon.Status |
||||
var err error |
||||
if len(u.statuses) == 0 { |
||||
statuses, err = u.app.API.GetUserStatuses(u.user) |
||||
} else { |
||||
statuses, _, err = u.app.API.GetUserStatusesOlder(u.user, u.statuses[len(u.statuses)-1]) |
||||
} |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
if len(statuses) == 0 { |
||||
return 0 |
||||
} |
||||
u.statuses = append(u.statuses, statuses...) |
||||
return len(statuses) |
||||
} |
||||
|
||||
func (u *User) DrawList() { |
||||
u.app.UI.StatusView.SetList(u.GetFeedList()) |
||||
} |
||||
|
||||
func (u *User) DrawToot() { |
||||
u.index = u.app.UI.StatusView.GetCurrentItem() |
||||
|
||||
var text string |
||||
var controls string |
||||
|
||||
if u.index == 0 { |
||||
n := fmt.Sprintf("[#%x]", u.app.Config.Style.Text.Hex()) |
||||
s1 := fmt.Sprintf("[#%x]", u.app.Config.Style.TextSpecial1.Hex()) |
||||
s2 := fmt.Sprintf("[#%x]", u.app.Config.Style.TextSpecial2.Hex()) |
||||
|
||||
if u.user.DisplayName != "" { |
||||
text = fmt.Sprintf(s2+"%s\n", u.user.DisplayName) |
||||
} |
||||
text += fmt.Sprintf(s1+"%s\n\n", u.user.Acct) |
||||
|
||||
text += fmt.Sprintf("Toots %s%d %sFollowers %s%d %sFollowing %s%d\n\n", |
||||
s2, u.user.StatusesCount, n, s2, u.user.FollowersCount, n, s2, u.user.FollowingCount) |
||||
|
||||
note, urls := cleanTootHTML(u.user.Note) |
||||
text += note + "\n\n" |
||||
|
||||
for _, f := range u.user.Fields { |
||||
value, fu := cleanTootHTML(f.Value) |
||||
text += fmt.Sprintf("%s%s: %s%s\n", s2, f.Name, n, value) |
||||
urls = append(urls, fu...) |
||||
} |
||||
|
||||
u.app.UI.LinkOverlay.SetURLs(urls) |
||||
|
||||
var controlItems []string |
||||
if u.app.Me.ID != u.user.ID { |
||||
if u.relation.Following { |
||||
controlItems = append(controlItems, ColorKey(u.app.Config.Style, "Un", "F", "ollow")) |
||||
} else { |
||||
controlItems = append(controlItems, ColorKey(u.app.Config.Style, "", "F", "ollow")) |
||||
} |
||||
if u.relation.Blocking { |
||||
controlItems = append(controlItems, ColorKey(u.app.Config.Style, "Un", "B", "lock")) |
||||
} else { |
||||
controlItems = append(controlItems, ColorKey(u.app.Config.Style, "", "B", "lock")) |
||||
} |
||||
if u.relation.Muting { |
||||
controlItems = append(controlItems, ColorKey(u.app.Config.Style, "Un", "M", "ute")) |
||||
} else { |
||||
controlItems = append(controlItems, ColorKey(u.app.Config.Style, "", "M", "ute")) |
||||
} |
||||
if len(urls) > 0 { |
||||
controlItems = append(controlItems, ColorKey(u.app.Config.Style, "", "O", "pen")) |
||||
} |
||||
controls = strings.Join(controlItems, " ") |
||||
} |
||||
|
||||
} else { |
||||
status := u.GetCurrentStatus() |
||||
if status == nil { |
||||
text = "" |
||||
controls = "" |
||||
} else { |
||||
text, controls = showTootOptions(u.app, status, u.showSpoiler) |
||||
} |
||||
u.showSpoiler = false |
||||
} |
||||
|
||||
u.app.UI.StatusView.SetText(text) |
||||
u.app.UI.StatusView.SetControls(controls) |
||||
} |
||||
|
||||
func (u *User) redrawControls() { |
||||
var controls string |
||||
status := u.GetCurrentStatus() |
||||
if status == nil { |
||||
controls = "" |
||||
} else { |
||||
_, controls = showTootOptions(u.app, status, u.showSpoiler) |
||||
} |
||||
u.app.UI.StatusView.SetControls(controls) |
||||
} |
||||
|
||||
func (u *User) GetSavedIndex() int { |
||||
return u.index |
||||
} |
||||
|
||||
func (u *User) Input(event *tcell.EventKey) { |
||||
index := u.GetSavedIndex() |
||||
|
||||
if index == 0 { |
||||
if event.Key() == tcell.KeyRune { |
||||
switch event.Rune() { |
||||
case 'f', 'F': |
||||
var relation *mastodon.Relationship |
||||
var err error |
||||
if u.relation.Following { |
||||
relation, err = u.app.API.UnfollowUser(u.user) |
||||
} else { |
||||
relation, err = u.app.API.FollowUser(u.user) |
||||
} |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
u.relation = relation |
||||
u.DrawToot() |
||||
case 'b', 'B': |
||||
var relation *mastodon.Relationship |
||||
var err error |
||||
if u.relation.Blocking { |
||||
relation, err = u.app.API.UnblockUser(u.user) |
||||
} else { |
||||
relation, err = u.app.API.BlockUser(u.user) |
||||
} |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
u.relation = relation |
||||
u.DrawToot() |
||||
case 'm', 'M': |
||||
var relation *mastodon.Relationship |
||||
var err error |
||||
if u.relation.Muting { |
||||
relation, err = u.app.API.UnmuteUser(u.user) |
||||
} else { |
||||
relation, err = u.app.API.MuteUser(u.user) |
||||
} |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
u.relation = relation |
||||
u.DrawToot() |
||||
case 'r', 'R': |
||||
//toots and replies?
|
||||
case 'o', 'O': |
||||
u.app.UI.ShowLinks() |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
if event.Key() == tcell.KeyRune { |
||||
status := u.GetCurrentStatus() |
||||
if status == nil { |
||||
return |
||||
} |
||||
switch event.Rune() { |
||||
case 't', 'T': |
||||
u.app.UI.StatusView.AddFeed( |
||||
NewThread(u.app, status), |
||||
) |
||||
case 'u', 'U': |
||||
if u.user.ID != status.Account.ID { |
||||
u.app.UI.StatusView.AddFeed( |
||||
NewUser(u.app, status.Account), |
||||
) |
||||
} |
||||
case 's', 'S': |
||||
u.showSpoiler = true |
||||
u.DrawToot() |
||||
case 'c', 'C': |
||||
u.app.UI.NewToot() |
||||
case 'o', 'O': |
||||
u.app.UI.ShowLinks() |
||||
case 'r', 'R': |
||||
u.app.UI.Reply(status) |
||||
case 'm', 'M': |
||||
u.app.UI.OpenMedia(status) |
||||
case 'f', 'F': |
||||
index := u.app.UI.StatusView.GetCurrentItem() |
||||
newStatus, err := u.app.API.FavoriteToogle(status) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
u.statuses[index-1] = newStatus |
||||
u.redrawControls() |
||||
|
||||
case 'b', 'B': |
||||
index := u.app.UI.StatusView.GetCurrentItem() |
||||
newStatus, err := u.app.API.BoostToggle(status) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
u.statuses[index-1] = newStatus |
||||
u.redrawControls() |
||||
case 'd', 'D': |
||||
u.app.API.DeleteStatus(status) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func NewNoticifations(app *App) *Notifications { |
||||
n := &Notifications{ |
||||
app: app, |
||||
} |
||||
n.notifications, _ = n.app.API.GetNotifications() |
||||
return n |
||||
} |
||||
|
||||
type Notifications struct { |
||||
app *App |
||||
timelineType TimelineType |
||||
notifications []*mastodon.Notification |
||||
index int |
||||
showSpoiler bool |
||||
} |
||||
|
||||
func (n *Notifications) FeedType() FeedType { |
||||
return NotificationFeed |
||||
} |
||||
|
||||
func (n *Notifications) GetCurrentNotification() *mastodon.Notification { |
||||
index := n.app.UI.StatusView.GetCurrentItem() |
||||
if index >= len(n.notifications) { |
||||
return nil |
||||
} |
||||
return n.notifications[index] |
||||
} |
||||
|
||||
func (n *Notifications) GetFeedList() <-chan string { |
||||
ch := make(chan string) |
||||
notifications := n.notifications |
||||
go func() { |
||||
today := time.Now() |
||||
ty, tm, td := today.Date() |
||||
for _, item := range notifications { |
||||
sLocal := item.CreatedAt.Local() |
||||
sy, sm, sd := sLocal.Date() |
||||
format := "2006-01-02 15:04" |
||||
if ty == sy && tm == sm && td == sd { |
||||
format = "15:04" |
||||
} |
||||
content := fmt.Sprintf("%s %s", sLocal.Format(format), item.Account.Acct) |
||||
ch <- content |
||||
} |
||||
close(ch) |
||||
}() |
||||
return ch |
||||
} |
||||
|
||||
func (n *Notifications) LoadNewer() int { |
||||
var notifications []*mastodon.Notification |
||||
var err error |
||||
if len(n.notifications) == 0 { |
||||
notifications, err = n.app.API.GetNotifications() |
||||
} else { |
||||
notifications, _, err = n.app.API.GetNotificationsNewer(n.notifications[0]) |
||||
} |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
if len(notifications) == 0 { |
||||
return 0 |
||||
} |
||||
old := n.notifications |
||||
n.notifications = append(notifications, old...) |
||||
return len(notifications) |
||||
} |
||||
|
||||
func (n *Notifications) LoadOlder() int { |
||||
var notifications []*mastodon.Notification |
||||
var err error |
||||
if len(n.notifications) == 0 { |
||||
notifications, err = n.app.API.GetNotifications() |
||||
} else { |
||||
notifications, _, err = n.app.API.GetNotificationsOlder(n.notifications[len(n.notifications)-1]) |
||||
} |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
if len(notifications) == 0 { |
||||
return 0 |
||||
} |
||||
n.notifications = append(n.notifications, notifications...) |
||||
return len(notifications) |
||||
} |
||||
|
||||
func (n *Notifications) DrawList() { |
||||
n.app.UI.StatusView.SetList(n.GetFeedList()) |
||||
} |
||||
|
||||
func (n *Notifications) DrawToot() { |
||||
n.index = n.app.UI.StatusView.GetCurrentItem() |
||||
notification := n.GetCurrentNotification() |
||||
if notification == nil { |
||||
n.app.UI.StatusView.SetText("") |
||||
n.app.UI.StatusView.SetControls("") |
||||
return |
||||
} |
||||
var text string |
||||
var controls string |
||||
defer func() { n.showSpoiler = false }() |
||||
|
||||
switch notification.Type { |
||||
case "follow": |
||||
text = SublteText(n.app.Config.Style, FormatUsername(notification.Account)+" started following you\n\n") |
||||
controls = ColorKey(n.app.Config.Style, "", "U", "ser") |
||||
case "favourite": |
||||
pre := SublteText(n.app.Config.Style, FormatUsername(notification.Account)+" favorited your toot") + "\n\n" |
||||
text, controls = showTootOptions(n.app, notification.Status, n.showSpoiler) |
||||
text = pre + text |
||||
case "reblog": |
||||
pre := SublteText(n.app.Config.Style, FormatUsername(notification.Account)+" boosted your toot") + "\n\n" |
||||
text, controls = showTootOptions(n.app, notification.Status, n.showSpoiler) |
||||
text = pre + text |
||||
case "mention": |
||||
pre := SublteText(n.app.Config.Style, FormatUsername(notification.Account)+" mentioned you") + "\n\n" |
||||
text, controls = showTootOptions(n.app, notification.Status, n.showSpoiler) |
||||
text = pre + text |
||||
case "poll": |
||||
pre := SublteText(n.app.Config.Style, "A poll of yours or one you participated in has ended") + "\n\n" |
||||
text, controls = showTootOptions(n.app, notification.Status, n.showSpoiler) |
||||
text = pre + text |
||||
} |
||||
|
||||
n.app.UI.StatusView.SetText(text) |
||||
n.app.UI.StatusView.SetControls(controls) |
||||
} |
||||
|
||||
func (n *Notifications) redrawControls() { |
||||
notification := n.GetCurrentNotification() |
||||
if notification == nil { |
||||
n.app.UI.StatusView.SetControls("") |
||||
return |
||||
} |
||||
switch notification.Type { |
||||
case "favourite", "reblog", "mention", "poll": |
||||
_, controls := showTootOptions(n.app, notification.Status, n.showSpoiler) |
||||
n.app.UI.StatusView.SetControls(controls) |
||||
} |
||||
} |
||||
|
||||
func (n *Notifications) GetSavedIndex() int { |
||||
return n.index |
||||
} |
||||
|
||||
func (n *Notifications) Input(event *tcell.EventKey) { |
||||
notification := n.GetCurrentNotification() |
||||
if notification == nil { |
||||
return |
||||
} |
||||
if notification.Type == "follow" { |
||||
if event.Key() == tcell.KeyRune { |
||||
switch event.Rune() { |
||||
case 'u', 'U': |
||||
n.app.UI.StatusView.AddFeed( |
||||
NewUser(n.app, notification.Account), |
||||
) |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
if event.Key() == tcell.KeyRune { |
||||
switch event.Rune() { |
||||
case 't', 'T': |
||||
n.app.UI.StatusView.AddFeed( |
||||
NewThread(n.app, notification.Status), |
||||
) |
||||
case 'u', 'U': |
||||
n.app.UI.StatusView.AddFeed( |
||||
NewUser(n.app, notification.Account), |
||||
) |
||||
case 's', 'S': |
||||
n.showSpoiler = true |
||||
n.DrawToot() |
||||
case 'c', 'C': |
||||
n.app.UI.NewToot() |
||||
case 'o', 'O': |
||||
n.app.UI.ShowLinks() |
||||
case 'r', 'R': |
||||
n.app.UI.Reply(notification.Status) |
||||
case 'm', 'M': |
||||
n.app.UI.OpenMedia(notification.Status) |
||||
case 'f', 'F': |
||||
index := n.app.UI.StatusView.GetCurrentItem() |
||||
status, err := n.app.API.FavoriteToogle(notification.Status) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
n.notifications[index].Status = status |
||||
n.redrawControls() |
||||
|
||||
case 'b', 'B': |
||||
index := n.app.UI.StatusView.GetCurrentItem() |
||||
status, err := n.app.API.BoostToggle(notification.Status) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
n.notifications[index].Status = status |
||||
n.redrawControls() |
||||
case 'd', 'D': |
||||
n.app.API.DeleteStatus(notification.Status) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,12 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"github.com/gdamore/tcell" |
||||
"github.com/rivo/tview" |
||||
) |
||||
|
||||
type PaneView interface { |
||||
GetLeftView() tview.Primitive |
||||
GetRightView() tview.Primitive |
||||
Input(event *tcell.EventKey) *tcell.EventKey |
||||
} |
||||
@ -0,0 +1,268 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"github.com/gdamore/tcell" |
||||
"github.com/rivo/tview" |
||||
) |
||||
|
||||
func NewStatusView(app *App, tl TimelineType) *StatusView { |
||||
t := &StatusView{ |
||||
app: app, |
||||
timelineType: tl, |
||||
list: tview.NewList(), |
||||
text: tview.NewTextView(), |
||||
controls: tview.NewTextView(), |
||||
focus: LeftPaneFocus, |
||||
loadingNewer: false, |
||||
loadingOlder: false, |
||||
} |
||||
t.flex = tview.NewFlex().SetDirection(tview.FlexRow). |
||||
AddItem(t.text, 0, 9, false). |
||||
AddItem(t.controls, 1, 0, false) |
||||
|
||||
t.list.SetBackgroundColor(app.Config.Style.Background) |
||||
t.list.SetSelectedTextColor(app.Config.Style.ListSelectedText) |
||||
t.list.SetSelectedBackgroundColor(app.Config.Style.ListSelectedBackground) |
||||
t.list.ShowSecondaryText(false) |
||||
t.list.SetHighlightFullLine(true) |
||||
|
||||
t.list.SetChangedFunc(func(i int, _ string, _ string, _ rune) { |
||||
if app.HaveAccount { |
||||
t.showToot(i) |
||||
} |
||||
}) |
||||
|
||||
t.text.SetWordWrap(true).SetDynamicColors(true) |
||||
t.text.SetBackgroundColor(app.Config.Style.Background) |
||||
t.text.SetTextColor(app.Config.Style.Text) |
||||
t.controls.SetDynamicColors(true) |
||||
t.controls.SetBackgroundColor(app.Config.Style.Background) |
||||
return t |
||||
} |
||||
|
||||
type StatusView struct { |
||||
app *App |
||||
timelineType TimelineType |
||||
list *tview.List |
||||
flex *tview.Flex |
||||
text *tview.TextView |
||||
controls *tview.TextView |
||||
feeds []Feed |
||||
focus FocusAt |
||||
loadingNewer bool |
||||
loadingOlder bool |
||||
} |
||||
|
||||
func (t *StatusView) AddFeed(f Feed) { |
||||
t.feeds = append(t.feeds, f) |
||||
f.DrawList() |
||||
t.list.SetCurrentItem(f.GetSavedIndex()) |
||||
f.DrawToot() |
||||
} |
||||
|
||||
func (t *StatusView) RemoveLatestFeed() { |
||||
t.feeds = t.feeds[:len(t.feeds)-1] |
||||
feed := t.feeds[len(t.feeds)-1] |
||||
feed.DrawList() |
||||
t.list.SetCurrentItem(feed.GetSavedIndex()) |
||||
feed.DrawToot() |
||||
} |
||||
|
||||
func (t *StatusView) GetLeftView() tview.Primitive { |
||||
if len(t.feeds) > 0 { |
||||
feed := t.feeds[len(t.feeds)-1] |
||||
feed.DrawList() |
||||
feed.DrawToot() |
||||
} |
||||
return t.list |
||||
} |
||||
|
||||
func (t *StatusView) GetRightView() tview.Primitive { |
||||
return t.flex |
||||
} |
||||
|
||||
func (t *StatusView) GetTextWidth() int { |
||||
_, _, width, _ := t.text.GetInnerRect() |
||||
return width |
||||
} |
||||
|
||||
func (t *StatusView) GetCurrentItem() int { |
||||
return t.list.GetCurrentItem() |
||||
} |
||||
|
||||
func (t *StatusView) ScrollToBeginning() { |
||||
t.text.ScrollToBeginning() |
||||
} |
||||
|
||||
func (t *StatusView) inputBoth(event *tcell.EventKey) { |
||||
if event.Key() == tcell.KeyRune { |
||||
switch event.Rune() { |
||||
case 'q', 'Q': |
||||
if len(t.feeds) > 1 { |
||||
t.RemoveLatestFeed() |
||||
} else { |
||||
t.app.UI.Root.Stop() |
||||
} |
||||
} |
||||
} else { |
||||
switch event.Key() { |
||||
case tcell.KeyCtrlC: |
||||
t.app.UI.Root.Stop() |
||||
} |
||||
} |
||||
if len(t.feeds) > 0 { |
||||
feed := t.feeds[len(t.feeds)-1] |
||||
feed.Input(event) |
||||
} |
||||
} |
||||
|
||||
func (t *StatusView) inputLeft(event *tcell.EventKey) { |
||||
if event.Key() == tcell.KeyRune { |
||||
switch event.Rune() { |
||||
case 'v', 'V': |
||||
t.app.UI.FocusAt(t.text, "--VIEW--") |
||||
t.focus = RightPaneFocus |
||||
case 'k', 'K': |
||||
t.prev() |
||||
case 'j', 'J': |
||||
t.next() |
||||
} |
||||
} else { |
||||
switch event.Key() { |
||||
case tcell.KeyUp: |
||||
t.prev() |
||||
case tcell.KeyDown: |
||||
t.next() |
||||
case tcell.KeyEsc: |
||||
if len(t.feeds) > 1 { |
||||
t.RemoveLatestFeed() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (t *StatusView) inputRight(event *tcell.EventKey) { |
||||
if event.Key() == tcell.KeyRune { |
||||
switch event.Rune() { |
||||
|
||||
} |
||||
} else { |
||||
switch event.Key() { |
||||
case tcell.KeyEsc: |
||||
t.app.UI.FocusAt(nil, "--LIST--") |
||||
t.focus = LeftPaneFocus |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (t *StatusView) Input(event *tcell.EventKey) *tcell.EventKey { |
||||
t.inputBoth(event) |
||||
if len(t.feeds) == 0 { |
||||
return event |
||||
} |
||||
|
||||
if t.focus == LeftPaneFocus { |
||||
t.inputLeft(event) |
||||
return nil |
||||
} else { |
||||
t.inputRight(event) |
||||
} |
||||
|
||||
return event |
||||
} |
||||
|
||||
func (t *StatusView) SetList(items <-chan string) { |
||||
t.list.Clear() |
||||
for s := range items { |
||||
t.list.AddItem(s, "", 0, nil) |
||||
} |
||||
} |
||||
func (t *StatusView) SetText(text string) { |
||||
t.text.SetText(text) |
||||
} |
||||
|
||||
func (t *StatusView) SetControls(text string) { |
||||
t.controls.SetText(text) |
||||
} |
||||
|
||||
func (t *StatusView) showToot(index int) { |
||||
} |
||||
|
||||
func (t *StatusView) showTootOptions(index int, showSensitive bool) { |
||||
} |
||||
|
||||
func (t *StatusView) prev() { |
||||
current := t.list.GetCurrentItem() |
||||
if current-1 >= 0 { |
||||
current-- |
||||
} |
||||
t.list.SetCurrentItem(current) |
||||
t.feeds[len(t.feeds)-1].DrawToot() |
||||
|
||||
if current < 4 { |
||||
t.loadNewer() |
||||
} |
||||
} |
||||
|
||||
func (t *StatusView) next() { |
||||
t.list.SetCurrentItem( |
||||
t.list.GetCurrentItem() + 1, |
||||
) |
||||
t.feeds[len(t.feeds)-1].DrawToot() |
||||
|
||||
count := t.list.GetItemCount() |
||||
current := t.list.GetCurrentItem() |
||||
if (count - current + 1) < 5 { |
||||
t.loadOlder() |
||||
} |
||||
} |
||||
|
||||
func (t *StatusView) loadNewer() { |
||||
if t.loadingNewer { |
||||
return |
||||
} |
||||
t.loadingNewer = true |
||||
feedIndex := len(t.feeds) - 1 |
||||
go func() { |
||||
new := t.feeds[feedIndex].LoadNewer() |
||||
if new == 0 { |
||||
return |
||||
} |
||||
if feedIndex != len(t.feeds)-1 { |
||||
return |
||||
} |
||||
t.app.UI.Root.QueueUpdateDraw(func() { |
||||
index := t.list.GetCurrentItem() |
||||
t.feeds[feedIndex].DrawList() |
||||
newIndex := index + new |
||||
if index == 0 && t.feeds[feedIndex].FeedType() == UserFeed { |
||||
newIndex = 0 |
||||
} |
||||
t.list.SetCurrentItem(newIndex) |
||||
t.loadingNewer = false |
||||
}) |
||||
}() |
||||
} |
||||
|
||||
func (t *StatusView) loadOlder() { |
||||
if t.loadingOlder { |
||||
return |
||||
} |
||||
t.loadingOlder = true |
||||
feedIndex := len(t.feeds) - 1 |
||||
go func() { |
||||
new := t.feeds[feedIndex].LoadOlder() |
||||
if new == 0 { |
||||
return |
||||
} |
||||
if feedIndex != len(t.feeds)-1 { |
||||
return |
||||
} |
||||
t.app.UI.Root.QueueUpdateDraw(func() { |
||||
index := t.list.GetCurrentItem() |
||||
t.feeds[feedIndex].DrawList() |
||||
t.list.SetCurrentItem(index) |
||||
t.loadingOlder = false |
||||
}) |
||||
}() |
||||
} |
||||
@ -1,257 +0,0 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"time" |
||||
|
||||
"github.com/mattn/go-mastodon" |
||||
"github.com/rivo/tview" |
||||
) |
||||
|
||||
type TootListFocus int |
||||
|
||||
const ( |
||||
TootListFeedFocus TootListFocus = iota |
||||
TootListThreadFocus |
||||
) |
||||
|
||||
type TootList struct { |
||||
app *App |
||||
Index int |
||||
Statuses []*mastodon.Status |
||||
Thread []*mastodon.Status |
||||
ThreadIndex int |
||||
List *tview.List |
||||
Focus TootListFocus |
||||
loadingFeedOld bool |
||||
loadingFeedNew bool |
||||
} |
||||
|
||||
func NewTootList(app *App) *TootList { |
||||
t := &TootList{ |
||||
app: app, |
||||
Index: 0, |
||||
Focus: TootListFeedFocus, |
||||
List: tview.NewList(), |
||||
} |
||||
t.List.SetBackgroundColor(app.Config.Style.Background) |
||||
t.List.SetSelectedTextColor(app.Config.Style.ListSelectedText) |
||||
t.List.SetSelectedBackgroundColor(app.Config.Style.ListSelectedBackground) |
||||
t.List.ShowSecondaryText(false) |
||||
t.List.SetHighlightFullLine(true) |
||||
|
||||
t.List.SetChangedFunc(func(index int, _ string, _ string, _ rune) { |
||||
if app.HaveAccount { |
||||
app.UI.TootView.ShowToot(index) |
||||
} |
||||
}) |
||||
|
||||
return t |
||||
} |
||||
|
||||
func (t *TootList) GetStatuses() []*mastodon.Status { |
||||
if t.Focus == TootListThreadFocus { |
||||
return t.GetThread() |
||||
} |
||||
return t.GetFeed() |
||||
} |
||||
|
||||
func (t *TootList) GetStatus(index int) (*mastodon.Status, error) { |
||||
if t.Focus == TootListThreadFocus { |
||||
return t.GetThreadStatus(index) |
||||
} |
||||
return t.GetFeedStatus(index) |
||||
} |
||||
|
||||
func (t *TootList) SetFeedStatuses(s []*mastodon.Status) { |
||||
t.Statuses = s |
||||
t.Draw() |
||||
} |
||||
|
||||
func (t *TootList) PrependFeedStatuses(s []*mastodon.Status) { |
||||
t.Statuses = append(s, t.Statuses...) |
||||
t.SetFeedIndex( |
||||
t.GetFeedIndex() + len(s), |
||||
) |
||||
t.List.SetCurrentItem(t.GetFeedIndex()) |
||||
} |
||||
|
||||
func (t *TootList) AppendFeedStatuses(s []*mastodon.Status) { |
||||
t.Statuses = append(t.Statuses, s...) |
||||
} |
||||
|
||||
func (t *TootList) GetFeed() []*mastodon.Status { |
||||
return t.Statuses |
||||
} |
||||
|
||||
func (t *TootList) GetFeedStatus(index int) (*mastodon.Status, error) { |
||||
statuses := t.GetFeed() |
||||
if index < len(statuses) { |
||||
return statuses[index], nil |
||||
} |
||||
return nil, fmt.Errorf("no status with that index") |
||||
} |
||||
|
||||
func (t *TootList) GetIndex() int { |
||||
if t.Focus == TootListThreadFocus { |
||||
return t.GetThreadIndex() |
||||
} |
||||
return t.GetFeedIndex() |
||||
} |
||||
|
||||
func (t *TootList) SetIndex(index int) { |
||||
switch t.Focus { |
||||
case TootListFeedFocus: |
||||
t.SetFeedIndex(index) |
||||
case TootListThreadFocus: |
||||
t.SetThreadIndex(index) |
||||
} |
||||
} |
||||
|
||||
func (t *TootList) GetFeedIndex() int { |
||||
return t.Index |
||||
} |
||||
|
||||
func (t *TootList) SetFeedIndex(index int) { |
||||
t.Index = index |
||||
} |
||||
|
||||
func (t *TootList) GetThreadIndex() int { |
||||
return t.ThreadIndex |
||||
} |
||||
|
||||
func (t *TootList) SetThreadIndex(index int) { |
||||
t.ThreadIndex = index |
||||
} |
||||
|
||||
func (t *TootList) Prev() { |
||||
index := t.GetIndex() |
||||
statuses := t.GetStatuses() |
||||
|
||||
if index-1 > -1 { |
||||
index-- |
||||
} |
||||
|
||||
if index < 5 && t.Focus == TootListFeedFocus { |
||||
go func() { |
||||
if t.loadingFeedNew { |
||||
return |
||||
} |
||||
t.loadingFeedNew = true |
||||
t.app.UI.LoadNewer(statuses[0]) |
||||
t.app.UI.Root.QueueUpdateDraw(func() { |
||||
t.Draw() |
||||
t.loadingFeedNew = false |
||||
}) |
||||
}() |
||||
} |
||||
t.SetIndex(index) |
||||
t.List.SetCurrentItem(index) |
||||
} |
||||
|
||||
func (t *TootList) Next() { |
||||
index := t.GetIndex() |
||||
statuses := t.GetStatuses() |
||||
|
||||
if index+1 < len(statuses) { |
||||
index++ |
||||
} |
||||
|
||||
if (len(statuses)-index) < 10 && t.Focus == TootListFeedFocus { |
||||
go func() { |
||||
if t.loadingFeedOld || len(statuses) == 0 { |
||||
return |
||||
} |
||||
t.loadingFeedOld = true |
||||
t.app.UI.LoadOlder(statuses[len(statuses)-1]) |
||||
t.app.UI.Root.QueueUpdateDraw(func() { |
||||
t.Draw() |
||||
t.loadingFeedOld = false |
||||
}) |
||||
}() |
||||
} |
||||
t.SetIndex(index) |
||||
t.List.SetCurrentItem(index) |
||||
} |
||||
|
||||
func (t *TootList) Draw() { |
||||
t.List.Clear() |
||||
|
||||
var statuses []*mastodon.Status |
||||
var index int |
||||
|
||||
switch t.Focus { |
||||
case TootListFeedFocus: |
||||
statuses = t.GetFeed() |
||||
index = t.GetFeedIndex() |
||||
case TootListThreadFocus: |
||||
statuses = t.GetThread() |
||||
index = t.GetThreadIndex() |
||||
} |
||||
if len(statuses) == 0 { |
||||
return |
||||
} |
||||
|
||||
today := time.Now() |
||||
ty, tm, td := today.Date() |
||||
currRow := 0 |
||||
for _, s := range statuses { |
||||
sLocal := s.CreatedAt.Local() |
||||
sy, sm, sd := sLocal.Date() |
||||
format := "2006-01-02 15:04" |
||||
if ty == sy && tm == sm && td == sd { |
||||
format = "15:04" |
||||
} |
||||
content := fmt.Sprintf("%s %s", sLocal.Format(format), s.Account.Acct) |
||||
t.List.InsertItem(currRow, content, "", 0, nil) |
||||
currRow++ |
||||
} |
||||
t.List.SetCurrentItem(index) |
||||
t.app.UI.TootView.ShowToot(index) |
||||
} |
||||
|
||||
func (t *TootList) SetThread(s []*mastodon.Status, index int) { |
||||
t.Thread = s |
||||
t.SetThreadIndex(index) |
||||
} |
||||
|
||||
func (t *TootList) GetThread() []*mastodon.Status { |
||||
return t.Thread |
||||
} |
||||
|
||||
func (t *TootList) GetThreadStatus(index int) (*mastodon.Status, error) { |
||||
statuses := t.GetThread() |
||||
if index < len(statuses) { |
||||
return statuses[index], nil |
||||
} |
||||
return nil, fmt.Errorf("no status with that index") |
||||
} |
||||
|
||||
func (t *TootList) FocusFeed() { |
||||
t.Focus = TootListFeedFocus |
||||
} |
||||
|
||||
func (t *TootList) FocusThread() { |
||||
t.Focus = TootListThreadFocus |
||||
} |
||||
|
||||
func (t *TootList) GoBack() { |
||||
t.Focus = TootListFeedFocus |
||||
t.Draw() |
||||
} |
||||
|
||||
func (t *TootList) Reply() { |
||||
status, err := t.GetStatus(t.GetIndex()) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
if status.Reblog != nil { |
||||
status = status.Reblog |
||||
} |
||||
|
||||
users := []string{"@" + status.Account.Acct} |
||||
for _, m := range status.Mentions { |
||||
users = append(users, "@"+m.Acct) |
||||
} |
||||
} |
||||
@ -1,167 +0,0 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"strings" |
||||
|
||||
"github.com/rivo/tview" |
||||
) |
||||
|
||||
func NewTootView(app *App) *TootView { |
||||
t := &TootView{ |
||||
app: app, |
||||
Index: 0, |
||||
Text: tview.NewTextView(), |
||||
Controls: tview.NewTextView(), |
||||
} |
||||
|
||||
t.Text.SetWordWrap(true).SetDynamicColors(true) |
||||
t.Text.SetBackgroundColor(app.Config.Style.Background) |
||||
t.Text.SetTextColor(app.Config.Style.Text) |
||||
t.Controls.SetDynamicColors(true) |
||||
t.Controls.SetBackgroundColor(app.Config.Style.Background) |
||||
|
||||
return t |
||||
} |
||||
|
||||
type TootView struct { |
||||
app *App |
||||
Index int |
||||
Text *tview.TextView |
||||
Controls *tview.TextView |
||||
} |
||||
|
||||
func (s *TootView) ShowToot(index int) { |
||||
s.ShowTootOptions(index, false) |
||||
} |
||||
|
||||
func (s *TootView) ShowTootOptions(index int, showSensitive bool) { |
||||
status, err := s.app.UI.TootList.GetStatus(index) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
|
||||
var line string |
||||
_, _, width, _ := s.Text.GetInnerRect() |
||||
for i := 0; i < width; i++ { |
||||
line += "-" |
||||
} |
||||
line += "\n" |
||||
|
||||
shouldDisplay := !status.Sensitive || showSensitive |
||||
|
||||
var stripped string |
||||
var urls []URL |
||||
var u []URL |
||||
if status.Sensitive && !showSensitive { |
||||
stripped, u = cleanTootHTML(status.SpoilerText) |
||||
urls = append(urls, u...) |
||||
stripped += "\n" + line |
||||
stripped += "Press [s] to show hidden text" |
||||
|
||||
} else { |
||||
stripped, u = cleanTootHTML(status.Content) |
||||
urls = append(urls, u...) |
||||
|
||||
if status.Sensitive { |
||||
sens, u := cleanTootHTML(status.SpoilerText) |
||||
urls = append(urls, u...) |
||||
stripped = sens + "\n\n" + stripped |
||||
} |
||||
} |
||||
s.app.UI.LinkOverlay.SetURLs(urls) |
||||
|
||||
subtleColor := fmt.Sprintf("[#%x]", s.app.Config.Style.Subtle.Hex()) |
||||
special1 := fmt.Sprintf("[#%x]", s.app.Config.Style.TextSpecial1.Hex()) |
||||
special2 := fmt.Sprintf("[#%x]", s.app.Config.Style.TextSpecial2.Hex()) |
||||
var head string |
||||
if status.Reblog != nil { |
||||
if status.Account.DisplayName != "" { |
||||
head += fmt.Sprintf(subtleColor+"%s (%s)\n", status.Account.DisplayName, status.Account.Acct) |
||||
} else { |
||||
head += fmt.Sprintf(subtleColor+"%s\n", status.Account.Acct) |
||||
} |
||||
head += subtleColor + "Boosted\n" |
||||
head += subtleColor + line |
||||
status = status.Reblog |
||||
} |
||||
|
||||
if status.Account.DisplayName != "" { |
||||
head += fmt.Sprintf(special2+"%s\n", status.Account.DisplayName) |
||||
} |
||||
head += fmt.Sprintf(special1+"%s\n\n", status.Account.Acct) |
||||
output := head |
||||
content := tview.Escape(stripped) |
||||
if content != "" { |
||||
output += content + "\n\n" |
||||
} |
||||
|
||||
var poll string |
||||
if status.Poll != nil { |
||||
poll += subtleColor + "Poll\n" |
||||
poll += subtleColor + line |
||||
poll += fmt.Sprintf("Number of votes: %d\n\n", status.Poll.VotesCount) |
||||
votes := float64(status.Poll.VotesCount) |
||||
for _, o := range status.Poll.Options { |
||||
res := 0.0 |
||||
if votes != 0 { |
||||
res = float64(o.VotesCount) / votes * 100 |
||||
} |
||||
poll += fmt.Sprintf("%s - %.2f%% (%d)\n", tview.Escape(o.Title), res, o.VotesCount) |
||||
} |
||||
poll += "\n" |
||||
} |
||||
|
||||
var media string |
||||
for _, att := range status.MediaAttachments { |
||||
media += subtleColor + line |
||||
media += fmt.Sprintf(subtleColor+"Attached %s\n", att.Type) |
||||
media += fmt.Sprintf("%s\n", att.URL) |
||||
} |
||||
|
||||
var card string |
||||
if status.Card != nil { |
||||
card += subtleColor + "Card type: " + status.Card.Type + "\n" |
||||
card += subtleColor + line |
||||
if status.Card.Title != "" { |
||||
card += status.Card.Title + "\n\n" |
||||
} |
||||
desc := strings.TrimSpace(status.Card.Description) |
||||
if desc != "" { |
||||
card += desc + "\n\n" |
||||
} |
||||
card += status.Card.URL |
||||
} |
||||
|
||||
if shouldDisplay { |
||||
output += poll + media + card |
||||
} |
||||
|
||||
s.Text.SetText(output) |
||||
s.Text.ScrollToBeginning() |
||||
var info []string |
||||
if status.Favourited == true { |
||||
info = append(info, "Un[F]avorite") |
||||
} else { |
||||
info = append(info, "[F]avorite") |
||||
} |
||||
if status.Reblogged == true { |
||||
info = append(info, "Un[B]oost") |
||||
} else { |
||||
info = append(info, "[B]oost") |
||||
} |
||||
info = append(info, "[T]hread", "[R]eply", "[V]iew") |
||||
if len(status.MediaAttachments) > 0 { |
||||
info = append(info, "[M]edia") |
||||
} |
||||
if len(urls) > 0 { |
||||
info = append(info, "[O]pen") |
||||
} |
||||
|
||||
if status.Account.ID == s.app.Me.ID { |
||||
info = append(info, "[D]elete") |
||||
} |
||||
|
||||
s.Controls.SetText(tview.Escape(strings.Join(info, " "))) |
||||
} |
||||
Loading…
Reference in new issue