TUI: Implement deleting own status messages

pull/122/head
Ivan Habunek 2019-09-04 16:16:16 +02:00
rodzic 7309e5bb53
commit 1d3ff87ffa
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: CDBD63C43A30BB95
5 zmienionych plików z 107 dodań i 26 usunięć

Wyświetl plik

@ -1,4 +1,9 @@
0.24.0:
date: TBA
changes:
- "TUI: Implement deleting own status messages"
0.23.1:
date: 2019-09-04
changes:

Wyświetl plik

@ -9,6 +9,7 @@ from .compose import StatusComposer
from .constants import PALETTE
from .entities import Status
from .overlays import ExceptionStackTrace, GotoMenu, Help, StatusSource
from .overlays import StatusDeleteConfirmation
from .timeline import Timeline
from .utils import show_media
@ -165,8 +166,11 @@ class TUI(urwid.Frame):
def _compose(*args):
self.show_compose()
def _delete(timeline, status):
if status.is_mine:
self.show_delete_confirmation(status)
def _reply(timeline, status):
logger.info("reply")
self.show_compose(status)
def _source(timeline, status):
@ -178,14 +182,15 @@ class TUI(urwid.Frame):
def _menu(timeline, status):
self.show_context_menu(status)
urwid.connect_signal(timeline, "focus", self.refresh_footer)
urwid.connect_signal(timeline, "reblog", self.async_toggle_reblog)
urwid.connect_signal(timeline, "favourite", self.async_toggle_favourite)
urwid.connect_signal(timeline, "source", _source)
urwid.connect_signal(timeline, "compose", _compose)
urwid.connect_signal(timeline, "reply", _reply)
urwid.connect_signal(timeline, "delete", _delete)
urwid.connect_signal(timeline, "favourite", self.async_toggle_favourite)
urwid.connect_signal(timeline, "focus", self.refresh_footer)
urwid.connect_signal(timeline, "media", _media)
urwid.connect_signal(timeline, "menu", _menu)
urwid.connect_signal(timeline, "reblog", self.async_toggle_reblog)
urwid.connect_signal(timeline, "reply", _reply)
urwid.connect_signal(timeline, "source", _source)
def build_timeline(self, name, statuses):
def _close(*args):
@ -206,6 +211,10 @@ class TUI(urwid.Frame):
return timeline
def make_status(self, status_data):
is_mine = self.user.username == status_data["account"]["acct"]
return Status(status_data, is_mine, self.app.instance)
def show_thread(self, status):
def _close(*args):
"""When thread is closed, go back to the main timeline."""
@ -216,8 +225,8 @@ class TUI(urwid.Frame):
# This is pretty fast, so it's probably ok to block while context is
# loaded, can be made async later if needed
context = api.context(self.app, self.user, status.id)
ancestors = [Status(s, self.app.instance) for s in context["ancestors"]]
descendants = [Status(s, self.app.instance) for s in context["descendants"]]
ancestors = [self.make_status(s) for s in context["ancestors"]]
descendants = [self.make_status(s) for s in context["descendants"]]
statuses = ancestors + [status] + descendants
focus = len(ancestors)
@ -241,7 +250,7 @@ class TUI(urwid.Frame):
finally:
self.footer.clear_message()
return [Status(s, self.app.instance) for s in data]
return [self.make_status(s) for s in data]
def _done_initial(statuses):
"""Process initial batch of statuses, construct a Timeline."""
@ -334,12 +343,28 @@ class TUI(urwid.Frame):
# TODO: show context menu
pass
def show_delete_confirmation(self, status):
def _delete(widget):
promise = self.async_delete_status(self.timeline, status)
promise.add_done_callback(lambda *args: self.close_overlay())
def _close(widget):
self.close_overlay()
widget = StatusDeleteConfirmation(status)
urwid.connect_signal(widget, "close", _close)
urwid.connect_signal(widget, "delete", _delete)
self.open_overlay(widget, title="Delete status?", options=dict(
align="center", width=("relative", 60),
valign="middle", height=5,
))
def post_status(self, content, warning, visibility, in_reply_to_id):
data = api.post_status(self.app, self.user, content,
spoiler_text=warning,
visibility=visibility,
in_reply_to_id=in_reply_to_id)
status = Status(data, self.app.instance)
status = self.make_status(data)
# TODO: instead of this, fetch new items from the timeline?
self.timeline.prepend_status(status)
@ -361,7 +386,8 @@ class TUI(urwid.Frame):
# Create a new Status with flipped favourited flag
new_data = status.data
new_data["favourited"] = not status.favourited
timeline.update_status(Status(new_data, status.instance))
new_status = self.make_status(new_data)
timeline.update_status(new_status)
self.run_in_thread(
_unfavourite if status.favourited else _favourite,
@ -381,13 +407,23 @@ class TUI(urwid.Frame):
# Create a new Status with flipped reblogged flag
new_data = status.data
new_data["reblogged"] = not status.reblogged
timeline.update_status(Status(new_data, status.instance))
new_status = self.make_status(new_data)
timeline.update_status(new_status)
self.run_in_thread(
_unreblog if status.reblogged else _reblog,
done_callback=_done
)
def async_delete_status(self, timeline, status):
def _delete():
api.delete_status(self.app, self.user, status.id)
def _done(loop):
timeline.remove_status(status)
return self.run_in_thread(_delete, done_callback=_done)
# --- Overlay handling -----------------------------------------------------
default_overlay_options = dict(

Wyświetl plik

@ -6,23 +6,16 @@ from .utils import parse_datetime
Author = namedtuple("Author", ["account", "display_name"])
def get_author(data, instance):
# Show the author, not the persopn who reblogged
status = data["reblog"] or data
acct = status['account']['acct']
acct = acct if "@" in acct else "{}@{}".format(acct, instance)
return Author(acct, status['account']['display_name'])
class Status:
"""
A wrapper around the Status entity data fetched from Mastodon.
https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#status
"""
def __init__(self, data, instance):
def __init__(self, data, is_mine, default_instance):
self.data = data
self.instance = instance
self.is_mine = is_mine
self.default_instance = default_instance
# This can be toggled by the user
self.show_sensitive = False
@ -33,14 +26,21 @@ class Status:
self.display_name = self.data["account"]["display_name"]
self.account = self.get_account()
self.created_at = parse_datetime(data["created_at"])
self.author = get_author(data, instance)
self.author = self.get_author()
self.favourited = data.get("favourited", False)
self.reblogged = data.get("reblogged", False)
self.in_reply_to = data.get("in_reply_to_id")
def get_author(self):
# Show the author, not the persopn who reblogged
data = self.data["reblog"] or self.data
acct = data['account']['acct']
acct = acct if "@" in acct else "{}@{}".format(acct, self.default_instance)
return Author(acct, data['account']['display_name'])
def get_account(self):
acct = self.data['account']['acct']
return acct if "@" in acct else "{}@{}".format(acct, self.instance)
return acct if "@" in acct else "{}@{}".format(acct, self.default_instance)
def __repr__(self):
return "<Status id={}>".format(self.id)

Wyświetl plik

@ -30,6 +30,23 @@ class ExceptionStackTrace(urwid.ListBox):
super().__init__(walker)
class StatusDeleteConfirmation(urwid.ListBox):
signals = ["delete", "close"]
def __init__(self, status):
yes = SelectableText("Yes, send it to heck")
no = SelectableText("No, I'll spare it for now")
urwid.connect_signal(yes, "click", lambda *args: self._emit("delete"))
urwid.connect_signal(no, "click", lambda *args: self._emit("close"))
walker = urwid.SimpleFocusListWalker([
urwid.AttrWrap(yes, "", "blue_selected"),
urwid.AttrWrap(no, "", "blue_selected"),
])
super().__init__(walker)
class GotoMenu(urwid.ListBox):
signals = [
"home_timeline",

Wyświetl plik

@ -17,6 +17,7 @@ class Timeline(urwid.Columns):
signals = [
"close", # Close thread
"compose", # Compose a new toot
"delete", # Delete own status
"favourite", # Favourite status
"focus", # Focus changed
"media", # Display media attachments
@ -110,6 +111,10 @@ class Timeline(urwid.Columns):
self._emit("compose")
return
if key in ("d", "D"):
self._emit("delete", status)
return
if key in ("f", "F"):
self._emit("favourite", status)
return
@ -184,6 +189,14 @@ class Timeline(urwid.Columns):
if index == self.status_list.body.focus:
self.draw_status_details(status)
def remove_status(self, status):
index = self.get_status_index(status.id)
assert self.statuses[index].id == status.id # Sanity check
del(self.statuses[index])
del(self.status_list.body[index])
self.refresh_status_details()
class StatusDetails(urwid.Pile):
def __init__(self, status, in_thread):
@ -247,8 +260,18 @@ class StatusDetails(urwid.Pile):
# Push things to bottom
yield ("weight", 1, urwid.SolidFill(" "))
options = "[B]oost [F]avourite [V]iew {}[R]eply So[u]rce [H]elp".format(
"[T]hread " if not self.in_thread else "")
options = [
"[B]oost",
"[D]elete" if status.is_mine else "",
"[F]avourite",
"[V]iew",
"[T]hread" if not self.in_thread else "",
"[R]eply",
"So[u]rce",
"[H]elp",
]
options = " ".join(o for o in options if o)
options = highlight_keys(options, "cyan_bold", "cyan")
yield ("pack", urwid.Text(options))