Implement character count indicator when composing
Attempts to load max char count from the server on init. issue #121
This commit is contained in:
parent
5695fdb504
commit
60efc13338
4 changed files with 53 additions and 15 deletions
|
@ -99,6 +99,9 @@ class TUI(urwid.Frame):
|
||||||
self.footer = Footer()
|
self.footer = Footer()
|
||||||
self.footer.set_status("Loading...")
|
self.footer.set_status("Loading...")
|
||||||
|
|
||||||
|
# Default max status length, updated on startup
|
||||||
|
self.max_toot_chars = 500
|
||||||
|
|
||||||
self.timeline = None
|
self.timeline = None
|
||||||
self.overlay = None
|
self.overlay = None
|
||||||
self.exception = None
|
self.exception = None
|
||||||
|
@ -106,8 +109,9 @@ class TUI(urwid.Frame):
|
||||||
super().__init__(self.body, header=self.header, footer=self.footer)
|
super().__init__(self.body, header=self.header, footer=self.footer)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.loop.set_alarm_in(0, lambda *args:
|
self.loop.set_alarm_in(0, lambda *args: self.async_load_instance())
|
||||||
self.async_load_timeline(is_initial=True, timeline_name="home"))
|
self.loop.set_alarm_in(0, lambda *args: self.async_load_timeline(
|
||||||
|
is_initial=True, timeline_name="home"))
|
||||||
self.loop.run()
|
self.loop.run()
|
||||||
self.executor.shutdown(wait=False)
|
self.executor.shutdown(wait=False)
|
||||||
|
|
||||||
|
@ -269,6 +273,21 @@ class TUI(urwid.Frame):
|
||||||
return self.run_in_thread(_load_statuses,
|
return self.run_in_thread(_load_statuses,
|
||||||
done_callback=_done_initial if is_initial else _done_next)
|
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):
|
def refresh_footer(self, timeline):
|
||||||
"""Show status details in footer."""
|
"""Show status details in footer."""
|
||||||
status, index, count = timeline.get_focused_status_with_counts()
|
status, index, count = timeline.get_focused_status_with_counts()
|
||||||
|
@ -296,7 +315,7 @@ class TUI(urwid.Frame):
|
||||||
def _post(timeline, *args):
|
def _post(timeline, *args):
|
||||||
self.post_status(*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, "close", _close)
|
||||||
urwid.connect_signal(composer, "post", _post)
|
urwid.connect_signal(composer, "post", _post)
|
||||||
self.open_overlay(composer, title="Compose status")
|
self.open_overlay(composer, title="Compose status")
|
||||||
|
|
|
@ -3,6 +3,7 @@ import logging
|
||||||
|
|
||||||
from .constants import VISIBILITY_OPTIONS
|
from .constants import VISIBILITY_OPTIONS
|
||||||
from .widgets import Button, EditBox
|
from .widgets import Button, EditBox
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,17 +13,16 @@ class StatusComposer(urwid.Frame):
|
||||||
"""
|
"""
|
||||||
signals = ["close", "post"]
|
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
|
self.in_reply_to = in_reply_to
|
||||||
text, edit_pos = '', None
|
self.max_chars = max_chars
|
||||||
if in_reply_to is not None:
|
|
||||||
text = '@{} '.format(in_reply_to.account)
|
text = self.get_initial_text(in_reply_to)
|
||||||
edit_pos = len(text)
|
self.content_edit = EditBox(
|
||||||
mentions = ['@{}'.format(m["acct"]) for m in in_reply_to.mentions]
|
edit_text=text, edit_pos=len(text), multiline=True, allow_tab=True)
|
||||||
if mentions:
|
urwid.connect_signal(self.content_edit.edit, "change", self.text_changed)
|
||||||
text += '\n\n{}'.format(' '.join(mentions))
|
|
||||||
self.content_edit = EditBox(edit_text=text, edit_pos=edit_pos,
|
self.char_count = urwid.Text(["0/{}".format(max_chars)])
|
||||||
multiline=True, allow_tab=True)
|
|
||||||
|
|
||||||
self.cw_edit = None
|
self.cw_edit = None
|
||||||
self.cw_add_button = Button("Add content warning",
|
self.cw_add_button = Button("Add content warning",
|
||||||
|
@ -42,6 +42,23 @@ class StatusComposer(urwid.Frame):
|
||||||
self.listbox = urwid.ListBox(self.walker)
|
self.listbox = urwid.ListBox(self.walker)
|
||||||
return super().__init__(self.listbox)
|
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):
|
def generate_list_items(self):
|
||||||
if self.in_reply_to:
|
if self.in_reply_to:
|
||||||
yield urwid.Text(("gray", "Replying to {}".format(self.in_reply_to.account)))
|
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 urwid.Text("Status message")
|
||||||
yield self.content_edit
|
yield self.content_edit
|
||||||
|
yield self.char_count
|
||||||
yield urwid.Divider()
|
yield urwid.Divider()
|
||||||
|
|
||||||
if self.cw_edit:
|
if self.cw_edit:
|
||||||
|
|
|
@ -34,6 +34,7 @@ PALETTE = [
|
||||||
('green_selected', 'white,bold', 'dark green'),
|
('green_selected', 'white,bold', 'dark green'),
|
||||||
('yellow', 'yellow', ''),
|
('yellow', 'yellow', ''),
|
||||||
('yellow_bold', 'yellow,bold', ''),
|
('yellow_bold', 'yellow,bold', ''),
|
||||||
|
('warning', 'light red', ''),
|
||||||
]
|
]
|
||||||
|
|
||||||
VISIBILITY_OPTIONS = [
|
VISIBILITY_OPTIONS = [
|
||||||
|
|
|
@ -32,8 +32,8 @@ class SelectableColumns(Clickable, urwid.Columns):
|
||||||
class EditBox(urwid.AttrWrap):
|
class EditBox(urwid.AttrWrap):
|
||||||
"""Styled edit box."""
|
"""Styled edit box."""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
edit = urwid.Edit(*args, **kwargs)
|
self.edit = urwid.Edit(*args, **kwargs)
|
||||||
return super().__init__(edit, "editbox", "editbox_focused")
|
return super().__init__(self.edit, "editbox", "editbox_focused")
|
||||||
|
|
||||||
|
|
||||||
class Button(urwid.AttrWrap):
|
class Button(urwid.AttrWrap):
|
||||||
|
|
Loading…
Reference in a new issue