From 60efc133386931b997c40b003a69e39fd552b9c3 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Fri, 3 Jan 2020 09:15:11 +0100 Subject: [PATCH] Implement character count indicator when composing Attempts to load max char count from the server on init. issue #121 --- toot/tui/app.py | 25 ++++++++++++++++++++++--- toot/tui/compose.py | 38 ++++++++++++++++++++++++++++---------- toot/tui/constants.py | 1 + toot/tui/widgets.py | 4 ++-- 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/toot/tui/app.py b/toot/tui/app.py index 651147c..8daa03c 100644 --- a/toot/tui/app.py +++ b/toot/tui/app.py @@ -99,6 +99,9 @@ class TUI(urwid.Frame): self.footer = Footer() self.footer.set_status("Loading...") + # Default max status length, updated on startup + self.max_toot_chars = 500 + self.timeline = None self.overlay = None self.exception = None @@ -106,8 +109,9 @@ class TUI(urwid.Frame): super().__init__(self.body, header=self.header, footer=self.footer) def run(self): - self.loop.set_alarm_in(0, lambda *args: - self.async_load_timeline(is_initial=True, timeline_name="home")) + self.loop.set_alarm_in(0, lambda *args: self.async_load_instance()) + self.loop.set_alarm_in(0, lambda *args: self.async_load_timeline( + is_initial=True, timeline_name="home")) self.loop.run() self.executor.shutdown(wait=False) @@ -269,6 +273,21 @@ class TUI(urwid.Frame): return self.run_in_thread(_load_statuses, done_callback=_done_initial if is_initial else _done_next) + def async_load_instance(self): + """ + Attempt to update max_toot_chars from instance data. + Does not work on vanilla Mastodon, works on Pleroma. + See: https://github.com/tootsuite/mastodon/issues/4915 + """ + def _load_instance(): + return api.get_instance(self.app.instance) + + def _done(instance): + if "max_toot_chars" in instance: + self.max_toot_chars + + return self.run_in_thread(_load_instance, done_callback=_done) + def refresh_footer(self, timeline): """Show status details in footer.""" status, index, count = timeline.get_focused_status_with_counts() @@ -296,7 +315,7 @@ class TUI(urwid.Frame): def _post(timeline, *args): self.post_status(*args) - composer = StatusComposer(in_reply_to) + composer = StatusComposer(self.max_toot_chars, in_reply_to) urwid.connect_signal(composer, "close", _close) urwid.connect_signal(composer, "post", _post) self.open_overlay(composer, title="Compose status") diff --git a/toot/tui/compose.py b/toot/tui/compose.py index 5ec8944..68228b1 100644 --- a/toot/tui/compose.py +++ b/toot/tui/compose.py @@ -3,6 +3,7 @@ import logging from .constants import VISIBILITY_OPTIONS from .widgets import Button, EditBox + logger = logging.getLogger(__name__) @@ -12,17 +13,16 @@ class StatusComposer(urwid.Frame): """ signals = ["close", "post"] - def __init__(self, in_reply_to=None): + def __init__(self, max_chars, in_reply_to=None): self.in_reply_to = in_reply_to - text, edit_pos = '', None - if in_reply_to is not None: - text = '@{} '.format(in_reply_to.account) - edit_pos = len(text) - mentions = ['@{}'.format(m["acct"]) for m in in_reply_to.mentions] - if mentions: - text += '\n\n{}'.format(' '.join(mentions)) - self.content_edit = EditBox(edit_text=text, edit_pos=edit_pos, - multiline=True, allow_tab=True) + self.max_chars = max_chars + + text = self.get_initial_text(in_reply_to) + self.content_edit = EditBox( + edit_text=text, edit_pos=len(text), multiline=True, allow_tab=True) + urwid.connect_signal(self.content_edit.edit, "change", self.text_changed) + + self.char_count = urwid.Text(["0/{}".format(max_chars)]) self.cw_edit = None self.cw_add_button = Button("Add content warning", @@ -42,6 +42,23 @@ class StatusComposer(urwid.Frame): self.listbox = urwid.ListBox(self.walker) return super().__init__(self.listbox) + def get_initial_text(self, in_reply_to): + if not in_reply_to: + return "" + + text = '@{} '.format(in_reply_to.account) + mentions = ['@{}'.format(m["acct"]) for m in in_reply_to.mentions] + if mentions: + text += '\n\n{}'.format(' '.join(mentions)) + + return text + + def text_changed(self, edit, text): + count = self.max_chars - len(text) + text = "{}/{}".format(count, self.max_chars) + color = "warning" if count < 0 else "" + self.char_count.set_text((color, text)) + def generate_list_items(self): if self.in_reply_to: yield urwid.Text(("gray", "Replying to {}".format(self.in_reply_to.account))) @@ -49,6 +66,7 @@ class StatusComposer(urwid.Frame): yield urwid.Text("Status message") yield self.content_edit + yield self.char_count yield urwid.Divider() if self.cw_edit: diff --git a/toot/tui/constants.py b/toot/tui/constants.py index edf88fe..346230d 100644 --- a/toot/tui/constants.py +++ b/toot/tui/constants.py @@ -34,6 +34,7 @@ PALETTE = [ ('green_selected', 'white,bold', 'dark green'), ('yellow', 'yellow', ''), ('yellow_bold', 'yellow,bold', ''), + ('warning', 'light red', ''), ] VISIBILITY_OPTIONS = [ diff --git a/toot/tui/widgets.py b/toot/tui/widgets.py index 59e1d0b..a311d52 100644 --- a/toot/tui/widgets.py +++ b/toot/tui/widgets.py @@ -32,8 +32,8 @@ class SelectableColumns(Clickable, urwid.Columns): class EditBox(urwid.AttrWrap): """Styled edit box.""" def __init__(self, *args, **kwargs): - edit = urwid.Edit(*args, **kwargs) - return super().__init__(edit, "editbox", "editbox_focused") + self.edit = urwid.Edit(*args, **kwargs) + return super().__init__(self.edit, "editbox", "editbox_focused") class Button(urwid.AttrWrap):