mirror of
https://github.com/ihabunek/toot
synced 2025-01-22 23:40:18 +01:00
Implement posting statuses
This commit is contained in:
parent
b95aca964f
commit
366e9382d3
@ -1,16 +1,24 @@
|
||||
maybe ???
|
||||
https://github.com/CanonicalLtd/subiquity/blob/master/subiquitycore/core.py#L280
|
||||
|
||||
educational:
|
||||
https://github.com/TomasTomecek/sen/blob/master/sen/tui/ui.py
|
||||
|
||||
check out:
|
||||
https://github.com/rndusr/stig/tree/master/stig/tui
|
||||
Interesting urwid implementations:
|
||||
* https://github.com/CanonicalLtd/subiquity/blob/master/subiquitycore/core.py#L280
|
||||
* https://github.com/TomasTomecek/sen/blob/master/sen/tui/ui.py
|
||||
* https://github.com/rndusr/stig/tree/master/stig/tui
|
||||
|
||||
TODO/Ideas:
|
||||
* pack left column in timeline view
|
||||
* when an error happens, show it in the status bar and have "press E to view exception" to show it in an overlay.
|
||||
* maybe even have error reporting? e.g. button to open an issue on github?
|
||||
* allow scrolling of toot contents if they don't fit the screen, perhaps using
|
||||
pageup/pagedown
|
||||
* consider adding semi-automated error reporting when viewing an exception,
|
||||
something along the lines of "press T to submit a ticket", which would link
|
||||
to a pre-filled issue submit page.
|
||||
* show new toots, some ideas:
|
||||
* R to reload/refresh timeline
|
||||
* streaming new toots? not sold on the idea
|
||||
* go up on first toot to fetch any newer ones, and prepend them?
|
||||
* Switch timeline to top/bottom layout for narrow views.
|
||||
* Think about how to show media
|
||||
* download media and use local image viewer?
|
||||
* convert to ascii art?
|
||||
* use signals to avoid tightly coupling components
|
||||
|
||||
Questions:
|
||||
* is it possible to make a span a urwid.Text selectable? e.g. for urls and hashtags
|
||||
|
@ -7,6 +7,7 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from toot import api, __version__
|
||||
|
||||
from .compose import StatusComposer
|
||||
from .constants import PALETTE
|
||||
from .entities import Status
|
||||
from .timeline import Timeline
|
||||
@ -225,6 +226,30 @@ class TUI(urwid.Frame):
|
||||
},
|
||||
)
|
||||
|
||||
def show_compose(self):
|
||||
composer = StatusComposer()
|
||||
urwid.connect_signal(composer, "close",
|
||||
lambda *args: self.close_overlay())
|
||||
urwid.connect_signal(composer, "post",
|
||||
lambda _, content, warning: self.post_status(content, warning))
|
||||
self.open_overlay(
|
||||
widget=composer,
|
||||
title="Compose status",
|
||||
options={
|
||||
"align": 'center',
|
||||
"width": ('relative', 80),
|
||||
"valign": 'middle',
|
||||
"height": ('relative', 80),
|
||||
},
|
||||
)
|
||||
|
||||
def post_status(self, content, warning):
|
||||
data = api.post_status(self.app, self.user, content, spoiler_text=warning)
|
||||
status = Status(data, self.app.instance)
|
||||
self.timeline.prepend_status(status)
|
||||
self.footer.set_message("Status posted {} \\o/".format(status.id))
|
||||
self.close_overlay()
|
||||
|
||||
def async_toggle_favourite(self, status):
|
||||
def _favourite():
|
||||
logger.info("Favouriting {}".format(status))
|
||||
@ -285,11 +310,16 @@ class TUI(urwid.Frame):
|
||||
# --- Keys -----------------------------------------------------------------
|
||||
|
||||
def unhandled_input(self, key):
|
||||
# TODO: this should not be in unhandled input
|
||||
if key in ('e', 'E'):
|
||||
if self.exception:
|
||||
self.show_exception(self.exception)
|
||||
|
||||
if key in ('q', 'Q'):
|
||||
elif key == 'esc':
|
||||
if self.overlay:
|
||||
self.close_overlay()
|
||||
|
||||
elif key in ('q', 'Q'):
|
||||
if self.overlay:
|
||||
self.close_overlay()
|
||||
else:
|
||||
|
87
toot/tui/compose.py
Normal file
87
toot/tui/compose.py
Normal file
@ -0,0 +1,87 @@
|
||||
import urwid
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EditBox(urwid.AttrWrap):
|
||||
def __init__(self):
|
||||
edit = urwid.Edit(multiline=True, allow_tab=True)
|
||||
return super().__init__(edit, "editbox", "editbox_focused")
|
||||
|
||||
|
||||
class Button(urwid.AttrWrap):
|
||||
def __init__(self, *args, **kwargs):
|
||||
button = urwid.Button(*args, **kwargs)
|
||||
padding = urwid.Padding(button, width=len(args[0]) + 4)
|
||||
return super().__init__(padding, "button", "button_focused")
|
||||
|
||||
def set_label(self, *args, **kwargs):
|
||||
self.original_widget.original_widget.set_label(*args, **kwargs)
|
||||
self.original_widget.width = len(args[0]) + 4
|
||||
|
||||
|
||||
class StatusComposer(urwid.Frame):
|
||||
signals = ["close", "post"]
|
||||
|
||||
def __init__(self):
|
||||
# This can be added by button press
|
||||
self.content = EditBox()
|
||||
self.content_warning = None
|
||||
self.cw_button = Button("Add content warning", on_press=self.toggle_cw)
|
||||
|
||||
contents = [
|
||||
urwid.Text("Status message"),
|
||||
self.content,
|
||||
urwid.Divider(),
|
||||
self.cw_button,
|
||||
Button("Post", on_press=self.post),
|
||||
Button("Cancel", on_press=self.close),
|
||||
]
|
||||
|
||||
self.walker = urwid.SimpleListWalker(contents)
|
||||
self.listbox = urwid.ListBox(self.walker)
|
||||
return super().__init__(self.listbox)
|
||||
|
||||
def toggle_cw(self, button):
|
||||
if self.content_warning:
|
||||
self.cw_button.set_label("Add content warning")
|
||||
self.walker.pop(2)
|
||||
self.walker.pop(2)
|
||||
self.walker.pop(2)
|
||||
self.walker.set_focus(3)
|
||||
self.content_warning = None
|
||||
else:
|
||||
self.cw_button.set_label("Remove content warning")
|
||||
self.content_warning = EditBox()
|
||||
self.walker.insert(2, self.content_warning)
|
||||
self.walker.insert(2, urwid.Text("Content warning"))
|
||||
self.walker.insert(2, urwid.Divider())
|
||||
self.walker.set_focus(4)
|
||||
|
||||
def clear_error_message(self):
|
||||
self.footer = None
|
||||
|
||||
def set_error_message(self, msg):
|
||||
self.footer = urwid.Text(("footer_message_error", msg))
|
||||
|
||||
def post(self, button):
|
||||
self.clear_error_message()
|
||||
|
||||
# Don't lstrip content to avoid removing intentional leading whitespace
|
||||
# However, do strip both sides to check if there is any content there
|
||||
content = self.content.edit_text.rstrip()
|
||||
content = None if not content.strip() else content
|
||||
|
||||
warning = (self.content_warning.edit_text.rstrip()
|
||||
if self.content_warning else "")
|
||||
warning = None if not warning.strip() else warning
|
||||
|
||||
if not content:
|
||||
self.set_error_message("Cannot post an empty message")
|
||||
return
|
||||
|
||||
self._emit("post", content, warning)
|
||||
|
||||
def close(self, button):
|
||||
self._emit("close")
|
@ -1,8 +1,12 @@
|
||||
# name, fg, bg, mono, fg_h, bg_h
|
||||
PALETTE = [
|
||||
# Components
|
||||
('button', 'white', 'black'),
|
||||
('button_focused', 'light gray', 'dark magenta'),
|
||||
('editbox', 'white', 'black'),
|
||||
('editbox_focused', '', 'dark magenta'),
|
||||
('footer_message', 'dark green', ''),
|
||||
('footer_message_error', 'white', 'dark red'),
|
||||
('footer_message_error', 'light red', ''),
|
||||
('footer_status', 'white', 'dark blue'),
|
||||
('footer_status_bold', 'white, bold', 'dark blue'),
|
||||
('header', 'white', 'dark blue'),
|
||||
|
@ -2,7 +2,6 @@ import logging
|
||||
import urwid
|
||||
import webbrowser
|
||||
|
||||
from toot import api
|
||||
from toot.utils import format_content
|
||||
|
||||
from .utils import highlight_hashtags
|
||||
@ -14,9 +13,6 @@ logger = logging.getLogger("toot")
|
||||
class Timeline(urwid.Columns):
|
||||
"""
|
||||
Displays a list of statuses to the left, and status details on the right.
|
||||
|
||||
TODO: Switch to top/bottom for narrow views.
|
||||
TODO: Cache rendered statuses?
|
||||
"""
|
||||
signals = [
|
||||
"status_focused",
|
||||
@ -68,13 +64,16 @@ class Timeline(urwid.Columns):
|
||||
def status_focused(self):
|
||||
"""Called when the list focus switches to a new status"""
|
||||
status = self.get_focused_status()
|
||||
self.status_details = StatusDetails(status)
|
||||
self.contents[1] = self.status_details, ("weight", 50, False)
|
||||
self.draw_status_details(status)
|
||||
|
||||
index = self.status_list.body.focus
|
||||
count = len(self.statuses)
|
||||
self._emit("status_focused", [status, index, count])
|
||||
|
||||
def draw_status_details(self, status):
|
||||
self.status_details = StatusDetails(status)
|
||||
self.contents[1] = self.status_details, ("weight", 50, False)
|
||||
|
||||
def keypress(self, size, key):
|
||||
# If down is pressed on last status in list emit a signal to load more.
|
||||
# TODO: Consider pre-loading statuses earlier
|
||||
@ -90,6 +89,10 @@ class Timeline(urwid.Columns):
|
||||
self.tui.async_toggle_reblog(status)
|
||||
return
|
||||
|
||||
if key in ('c', 'C'):
|
||||
self.tui.show_compose()
|
||||
return
|
||||
|
||||
if key in ("f", "F"):
|
||||
status = self.get_focused_status()
|
||||
self.tui.async_toggle_favourite(status)
|
||||
@ -112,6 +115,17 @@ class Timeline(urwid.Columns):
|
||||
self.status_index_map[status.id] = len(self.statuses) - 1
|
||||
self.status_list.body.append(self.build_list_item(status))
|
||||
|
||||
def prepend_status(self, status):
|
||||
self.statuses.insert(0, status)
|
||||
# Need to rebuild the map, there has to be a better way
|
||||
self.status_index_map = {
|
||||
status.id: n for n, status in enumerate(self.statuses)
|
||||
}
|
||||
self.status_list.body.insert(0, self.build_list_item(status))
|
||||
|
||||
if self.status_list.body.focus == 0:
|
||||
self.draw_status_details(status)
|
||||
|
||||
def add_statuses(self, statuses):
|
||||
for status in statuses:
|
||||
self.add_status(status)
|
||||
@ -129,8 +143,8 @@ class Timeline(urwid.Columns):
|
||||
|
||||
# Redraw status details if status is focused
|
||||
if index == self.status_list.body.focus:
|
||||
self.status_details = StatusDetails(status)
|
||||
self.contents[1] = self.status_details, ("weight", 50, False)
|
||||
self.draw_status_details(status)
|
||||
|
||||
|
||||
class StatusDetails(urwid.Pile):
|
||||
def __init__(self, status):
|
||||
@ -161,7 +175,8 @@ class StatusDetails(urwid.Pile):
|
||||
yield ("pack", urwid.Text([
|
||||
("cyan_bold", "B"), ("cyan", "oost"), " | ",
|
||||
("cyan_bold", "F"), ("cyan", "avourite"), " | ",
|
||||
("cyan_bold", "V"), ("cyan", "iew in browser"), " | ",
|
||||
("cyan_bold", "V"), ("cyan", "iew"), " | ",
|
||||
("cyan", "So"), ("cyan_bold", "u"), ("cyan", "rce"), " | ",
|
||||
("cyan_bold", "H"), ("cyan", "elp"), " ",
|
||||
]))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user