Browse Source

edit toots (#188)

pull/193/head 1.0.21
Rasmus Lindroth 3 years ago committed by GitHub
parent
commit
ed7c99cd70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      README.md
  2. 10
      config.example.ini
  3. 6
      config/config.go
  4. 10
      config/default_config.go
  5. 2
      main.go
  6. 5
      ui/cmdbar.go
  7. 18
      ui/commands.go
  8. 133
      ui/composeview.go
  9. 10
      ui/input.go
  10. 3
      ui/item_status.go
  11. 18
      ui/pollview.go
  12. 8
      ui/view.go

1
README.md

@ -43,6 +43,7 @@ You can find Linux binaries under [releases](https://github.com/RasmusLindroth/t
* `:bookmarks` lists all your bookmarks
* `:clear-notifications` clear all notifications
* `:compose` compose a new toot
* `:edit` edit one of your toots
* `:favorited` lists toots you've favorited
* `:favorites` lists users that favorited the toot
* `:follow-tag` followed by the hashtag to follow e.g. `:follow-tag tut`

10
config.example.ini

@ -150,9 +150,9 @@ leader-timeout=1000
# comma.
#
# Available commands: home, direct, local, federated, clear-notifications,
# compose, history, blocking, bookmarks, saved, favorited, boosts, favorites,
# following, followers, muting, newer, preferences, profile, notifications,
# lists, tag, window, list-placement, list-split, proportions
# compose, edit, history, blocking, bookmarks, saved, favorited, boosts,
# favorites, following, followers, muting, newer, preferences, profile,
# notifications, lists, tag, window, list-placement, list-split, proportions
#
# The shortcuts are up to you, but keep them quite short and make sure they
# don't collide. If you have one shortcut that is "f" and an other one that is
@ -554,6 +554,10 @@ status-avatar="[A]vatar",'a','A'
# default="[B]oost","Un[B]oost",'b','B'
status-boost="[B]oost","Un[B]oost",'b','B'
# Edit a toot
# default="[E]dit",'e','E'
status-edit="[E]dit",'e','E'
# Delete a toot
# default="[D]elete",'d','D'
status-delete="[D]elete",'d','D'

6
config/config.go

@ -57,6 +57,7 @@ const (
LeaderFederated
LeaderClearNotifications
LeaderCompose
LeaderEdit
LeaderBlocking
LeaderBookmarks
LeaderSaved
@ -358,6 +359,7 @@ type Input struct {
StatusAvatar Key
StatusBoost Key
StatusDelete Key
StatusEdit Key
StatusFavorite Key
StatusMedia Key
StatusLinks Key
@ -860,6 +862,8 @@ func parseGeneral(cfg *ini.File) General {
la.Command = LeaderClearNotifications
case "compose":
la.Command = LeaderCompose
case "edit":
la.Command = LeaderEdit
case "blocking":
la.Command = LeaderBlocking
case "bookmarks":
@ -1246,6 +1250,7 @@ func parseInput(cfg *ini.File) Input {
StatusAvatar: inputStrOrErr([]string{"\"[A]vatar\"", "'a'", "'A'"}, false),
StatusBoost: inputStrOrErr([]string{"\"[B]oost\"", "\"Un[B]oost\"", "'b'", "'B'"}, true),
StatusDelete: inputStrOrErr([]string{"\"[D]elete\"", "'d'", "'D'"}, false),
StatusEdit: inputStrOrErr([]string{"\"[E]dit\"", "'e'", "'E'"}, false),
StatusFavorite: inputStrOrErr([]string{"\"[F]avorite\"", "\"Un[F]avorite\"", "'f'", "'F'"}, true),
StatusMedia: inputStrOrErr([]string{"\"[M]edia\"", "'m'", "'M'"}, false),
StatusLinks: inputStrOrErr([]string{"\"[O]pen\"", "'o'", "'O'"}, false),
@ -1323,6 +1328,7 @@ func parseInput(cfg *ini.File) Input {
ic.StatusAvatar = inputOrErr(cfg, "status-avatar", false, ic.StatusAvatar)
ic.StatusBoost = inputOrErr(cfg, "status-boost", true, ic.StatusBoost)
ic.StatusDelete = inputOrErr(cfg, "status-delete", false, ic.StatusDelete)
ic.StatusEdit = inputOrErr(cfg, "status-edit", false, ic.StatusEdit)
ic.StatusFavorite = inputOrErr(cfg, "status-favorite", true, ic.StatusFavorite)
ic.StatusMedia = inputOrErr(cfg, "status-media", false, ic.StatusMedia)
ic.StatusLinks = inputOrErr(cfg, "status-links", false, ic.StatusLinks)

10
config/default_config.go

@ -152,9 +152,9 @@ leader-timeout=1000
# comma.
#
# Available commands: home, direct, local, federated, clear-notifications,
# compose, history, blocking, bookmarks, saved, favorited, boosts, favorites,
# following, followers, muting, newer, preferences, profile, notifications,
# lists, tag, window, list-placement, list-split, proportions
# compose, edit, history, blocking, bookmarks, saved, favorited, boosts,
# favorites, following, followers, muting, newer, preferences, profile,
# notifications, lists, tag, window, list-placement, list-split, proportions
#
# The shortcuts are up to you, but keep them quite short and make sure they
# don't collide. If you have one shortcut that is "f" and an other one that is
@ -556,6 +556,10 @@ status-avatar="[A]vatar",'a','A'
# default="[B]oost","Un[B]oost",'b','B'
status-boost="[B]oost","Un[B]oost",'b','B'
# Edit a toot
# default="[E]dit",'e','E'
status-edit="[E]dit",'e','E'
# Delete a toot
# default="[D]elete",'d','D'
status-delete="[D]elete",'d','D'

2
main.go

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

5
ui/cmdbar.go

@ -67,6 +67,11 @@ func (c *CmdBar) DoneFunc(key tcell.Key) {
c.tutView.ComposeCommand()
c.ClearInput()
c.View.Autocomplete()
case ":edit":
c.ClearInput()
c.View.Autocomplete()
c.Back()
c.tutView.EditCommand()
case ":blocking":
c.tutView.BlockingCommand()
c.Back()

18
ui/commands.go

@ -12,7 +12,23 @@ import (
)
func (tv *TutView) ComposeCommand() {
tv.InitPost(nil)
tv.InitPost(nil, nil)
}
func (tv *TutView) EditCommand() {
item, itemErr := tv.GetCurrentItem()
if itemErr != nil {
return
}
if item.Type() != api.StatusType {
return
}
s := item.Raw().(*mastodon.Status)
s = util.StatusOrReblog(s)
if tv.tut.Client.Me.ID != s.Account.ID {
return
}
tv.InitPost(nil, s)
}
func (tv *TutView) BlockingCommand() {

133
ui/composeview.go

@ -9,6 +9,7 @@ import (
"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"
@ -17,8 +18,10 @@ import (
)
type msgToot struct {
ID mastodon.ID
Text string
Status *mastodon.Status
Reply *mastodon.Status
Edit *mastodon.Status
MediaIDs []mastodon.ID
Sensitive bool
SpoilerText string
@ -123,7 +126,7 @@ func (cv *ComposeView) SetControls(ctrl ComposeControls) {
items = append(items, NewControl(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposeMediaFocus, true))
items = append(items, NewControl(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposePoll, true))
items = append(items, NewControl(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposeLanguage, true))
if cv.msg.Status != nil {
if cv.msg.Reply != nil {
items = append(items, NewControl(cv.tutView.tut.Config, cv.tutView.tut.Config.Input.ComposeIncludeQuote, true))
}
case ComposeMedia:
@ -142,7 +145,7 @@ func (cv *ComposeView) SetControls(ctrl ComposeControls) {
}
}
func (cv *ComposeView) SetStatus(status *mastodon.Status) {
func (cv *ComposeView) SetStatus(reply *mastodon.Status, edit *mastodon.Status) error {
cv.tutView.PollView.Reset()
cv.media.Reset()
msg := &msgToot{}
@ -155,30 +158,60 @@ func (cv *ComposeView) SetStatus(status *mastodon.Status) {
if me.Source != nil && me.Source.Language != nil {
lang = *me.Source.Language
}
if status != nil {
if status.Reblog != nil {
status = status.Reblog
if reply != nil {
if reply.Reblog != nil {
reply = reply.Reblog
}
msg.Status = status
if status.Sensitive {
msg.Reply = reply
if reply.Sensitive {
msg.Sensitive = true
msg.SpoilerText = status.SpoilerText
msg.SpoilerText = reply.SpoilerText
}
if visibilities[status.Visibility] > visibilities[visibility] {
visibility = status.Visibility
if visibilities[reply.Visibility] > visibilities[visibility] {
visibility = reply.Visibility
}
}
msg.Visibility = visibility
msg.Language = lang
cv.msg = msg
cv.msg.Text = cv.getAccs()
if cv.tutView.tut.Config.General.QuoteReply {
if edit != nil {
source, err := cv.tutView.tut.Client.Client.GetStatusSource(context.Background(), edit.ID)
if err != nil {
cv.tutView.ShowError(
fmt.Sprintf("Couldn't get status. Error: %v\n", err),
)
return err
}
msg := &msgToot{}
msg.Edit = edit
msg.ID = source.ID
msg.Text = source.Text
msg.SpoilerText = source.SpoilerText
for _, mid := range edit.MediaAttachments {
msg.MediaIDs = append(msg.MediaIDs, mid.ID)
}
msg.Sensitive = edit.Sensitive
msg.Visibility = edit.Visibility
msg.Language = edit.Language
if edit.Poll != nil {
cv.tutView.PollView.AddPoll(edit.Poll)
}
if len(edit.MediaAttachments) > 0 {
cv.media.AddFromEdit(edit)
}
cv.msg = msg
}
if cv.tutView.tut.Config.General.QuoteReply && edit == nil {
cv.IncludeQuote()
}
cv.visibility.SetLabel("Visibility: ")
index := 0
for i, v := range visibilitiesStr {
if msg.Visibility == v {
if cv.msg.Visibility == v {
index = i
break
}
@ -190,7 +223,7 @@ func (cv *ComposeView) SetStatus(status *mastodon.Status) {
cv.lang.SetLabel("Lang: ")
langStrs := []string{}
for i, l := range util.Languages {
if msg.Language == l.Code {
if cv.msg.Language == l.Code {
index = i
}
langStrs = append(langStrs, fmt.Sprintf("%s (%s)", l.Local, l.English))
@ -200,13 +233,14 @@ func (cv *ComposeView) SetStatus(status *mastodon.Status) {
cv.UpdateContent()
cv.SetControls(ComposeNormal)
return nil
}
func (cv *ComposeView) getAccs() string {
if cv.msg.Status == nil {
if cv.msg.Reply == nil {
return ""
}
s := cv.msg.Status
s := cv.msg.Reply
var users []string
if s.Account.Acct != cv.tutView.tut.Client.Me.Acct {
users = append(users, "@"+s.Account.Acct)
@ -259,12 +293,12 @@ func (cv *ComposeView) UpdateContent() {
var outputHead string
var output string
if cv.msg.Status != nil {
if cv.msg.Reply != nil {
var acct string
if cv.msg.Status.Account.DisplayName != "" {
acct = fmt.Sprintf("%s (%s)\n", cv.msg.Status.Account.DisplayName, cv.msg.Status.Account.Acct)
if cv.msg.Reply.Account.DisplayName != "" {
acct = fmt.Sprintf("%s (%s)\n", cv.msg.Reply.Account.DisplayName, cv.msg.Reply.Account.Acct)
} else {
acct = fmt.Sprintf("%s\n", cv.msg.Status.Account.Acct)
acct = fmt.Sprintf("%s\n", cv.msg.Reply.Account.Acct)
}
outputHead += subtleColor + "Replying to " + tview.Escape(acct) + "\n" + normal
}
@ -291,7 +325,7 @@ func (cv *ComposeView) IncludeQuote() {
return
}
t := cv.msg.Text
s := cv.msg.Status
s := cv.msg.Reply
if s == nil {
return
}
@ -383,8 +417,11 @@ func (cv *ComposeView) Post() {
send := mastodon.Toot{
Status: strings.TrimSpace(toot.Text),
}
if toot.Status != nil {
send.InReplyToID = toot.Status.ID
if toot.Reply != nil {
send.InReplyToID = toot.Reply.ID
}
if toot.Edit != nil && toot.Edit.InReplyToID != nil {
send.InReplyToID = toot.Edit.InReplyToID.(mastodon.ID)
}
if toot.Sensitive {
send.Sensitive = true
@ -394,6 +431,10 @@ func (cv *ComposeView) Post() {
if cv.HasMedia() {
attachments := cv.media.Files
for _, ap := range attachments {
if ap.Remote {
send.MediaIDs = append(send.MediaIDs, ap.ID)
continue
}
f, err := os.Open(ap.Path)
if err != nil {
cv.tutView.ShowError(
@ -426,7 +467,25 @@ func (cv *ComposeView) Post() {
send.Visibility = cv.msg.Visibility
send.Language = cv.msg.Language
_, err := cv.tutView.tut.Client.Client.PostStatus(context.Background(), &send)
var err error
var newPost *mastodon.Status
if toot.Edit != nil {
newPost, err = cv.tutView.tut.Client.Client.UpdateStatus(context.Background(), &send, toot.Edit.ID)
if err == nil {
item, itemErr := cv.tutView.GetCurrentItem()
if itemErr != nil {
return
}
if item.Type() != api.StatusType {
return
}
s := item.Raw().(*mastodon.Status)
*s = *newPost
cv.tutView.RedrawContent()
}
} else {
_, err = cv.tutView.tut.Client.Client.PostStatus(context.Background(), &send)
}
if err != nil {
cv.tutView.ShowError(
fmt.Sprintf("Couldn't post toot. Error: %v\n", err),
@ -466,6 +525,26 @@ func NewMediaList(tv *TutView) *MediaList {
type UploadFile struct {
Path string
Description string
Remote bool
ID mastodon.ID
}
func (m *MediaList) AddFromEdit(edit *mastodon.Status) {
m.Files = nil
m.list.Clear()
for i, ma := range edit.MediaAttachments {
m.Files = append(m.Files, UploadFile{
Description: ma.Description,
Remote: true,
ID: ma.ID,
})
m.list.AddItem(fmt.Sprintf("From edit: %d", i+1), "", 0, nil)
}
index := m.list.GetItemCount()
if index > 0 {
m.list.SetCurrentItem(index - 1)
}
m.Draw()
}
func (m *MediaList) Reset() {
@ -546,6 +625,12 @@ func (m *MediaList) EditDesc() {
return
}
file := m.Files[index]
if file.Remote {
m.tutView.ShowError(
"Can't edit desc of a file that's already uploaded",
)
return
}
desc, err := OpenEditor(m.tutView, file.Description)
if err != nil {
m.tutView.ShowError(

10
ui/input.go

@ -119,6 +119,8 @@ func (tv *TutView) InputLeaderKey(event *tcell.EventKey) *tcell.EventKey {
tv.ClearNotificationsCommand()
case config.LeaderCompose:
tv.ComposeCommand()
case config.LeaderEdit:
tv.EditCommand()
case config.LeaderBlocking:
tv.BlockingCommand()
case config.LeaderBookmarks, config.LeaderSaved:
@ -295,7 +297,7 @@ func (tv *TutView) InputItem(event *tcell.EventKey) *tcell.EventKey {
return event
}
if tv.tut.Config.Input.MainCompose.Match(event.Key(), event.Rune()) {
tv.InitPost(nil)
tv.InitPost(nil, nil)
return nil
}
switch item.Type() {
@ -405,6 +407,10 @@ func (tv *TutView) InputStatus(event *tcell.EventKey, item api.Item, status *mas
})
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 {
@ -443,7 +449,7 @@ func (tv *TutView) InputStatus(event *tcell.EventKey, item api.Item, status *mas
return nil
}
if tv.tut.Config.Input.StatusReply.Match(event.Key(), event.Rune()) {
tv.InitPost(status)
tv.InitPost(status, nil)
return nil
}
if tv.tut.Config.Input.StatusBookmark.Match(event.Key(), event.Rune()) {

3
ui/item_status.go

@ -214,6 +214,9 @@ func drawStatus(tv *TutView, item api.Item, status *mastodon.Status, main *tview
info = append(info, NewControl(tv.tut.Config, tv.tut.Config.Input.StatusLinks, true))
}
info = append(info, NewControl(tv.tut.Config, tv.tut.Config.Input.StatusAvatar, true))
if status.Account.ID == tv.tut.Client.Me.ID && !isHistory {
info = append(info, NewControl(tv.tut.Config, tv.tut.Config.Input.StatusEdit, true))
}
if status.Account.ID == tv.tut.Client.Me.ID && !isHistory {
info = append(info, NewControl(tv.tut.Config, tv.tut.Config.Input.StatusDelete, true))
}

18
ui/pollview.go

@ -2,6 +2,7 @@ package ui
import (
"fmt"
"time"
"github.com/RasmusLindroth/go-mastodon"
"github.com/gdamore/tcell/v2"
@ -101,6 +102,23 @@ func (p *PollView) Reset() {
p.redrawInfo()
}
func (p *PollView) AddPoll(np *mastodon.Poll) {
p.poll = &mastodon.TootPoll{
Options: []string{},
ExpiresInSeconds: durationsTime[durations[4]],
Multiple: false,
HideTotals: false,
}
for _, opt := range np.Options {
p.poll.Options = append(p.poll.Options, opt.Title)
p.list.AddItem(opt.Title, "", 0, nil)
}
p.poll.Multiple = np.Multiple
diff := time.Until(np.ExpiresAt)
p.poll.ExpiresInSeconds = int64(diff.Seconds())
p.redrawInfo()
}
func (p *PollView) HasPoll() bool {
return p.list.GetItemCount() > 1
}

8
ui/view.go

@ -165,9 +165,11 @@ func (tv *TutView) PrevFocus() {
tv.PrevPageFocus = MainFocus
}
func (tv *TutView) InitPost(status *mastodon.Status) {
tv.ComposeView.SetStatus(status)
tv.SetPage(ComposeFocus)
func (tv *TutView) InitPost(status *mastodon.Status, original *mastodon.Status) {
err := tv.ComposeView.SetStatus(status, original)
if err == nil {
tv.SetPage(ComposeFocus)
}
}
func (tv *TutView) ShowError(s string) {

Loading…
Cancel
Save