Add an action to display status links and open them

We add a new [L]inks action that opens an overlay window with links
found in the content of selected status. Links are selectable and upon
click/enter we open the web browser at link's URL.
This commit is contained in:
Denis Laxalde 2020-01-26 11:13:45 +01:00 committed by Ivan Habunek
parent 5fc46d0cfc
commit 28e1281187
4 changed files with 63 additions and 2 deletions

View File

@ -8,10 +8,10 @@ from toot import api, __version__
from .compose import StatusComposer
from .constants import PALETTE
from .entities import Status
from .overlays import ExceptionStackTrace, GotoMenu, Help, StatusSource
from .overlays import ExceptionStackTrace, GotoMenu, Help, StatusSource, StatusLinks
from .overlays import StatusDeleteConfirmation
from .timeline import Timeline
from .utils import show_media
from .utils import parse_content_links, show_media
logger = logging.getLogger(__name__)
@ -182,6 +182,9 @@ class TUI(urwid.Frame):
def _source(timeline, status):
self.show_status_source(status)
def _links(timeline, status):
self.show_links(status)
def _media(timeline, status):
self.show_media(status)
@ -197,6 +200,7 @@ class TUI(urwid.Frame):
urwid.connect_signal(timeline, "reblog", self.async_toggle_reblog)
urwid.connect_signal(timeline, "reply", _reply)
urwid.connect_signal(timeline, "source", _source)
urwid.connect_signal(timeline, "links", _links)
def build_timeline(self, name, statuses):
def _close(*args):
@ -302,6 +306,14 @@ class TUI(urwid.Frame):
title="Status source",
)
def show_links(self, status):
links = parse_content_links(status.data["content"])
self.open_overlay(
widget=StatusLinks(links),
title="Status links",
options={"height": len(links) + 2},
)
def show_exception(self, exception):
self.open_overlay(
widget=ExceptionStackTrace(exception),

View File

@ -20,6 +20,20 @@ class StatusSource(urwid.ListBox):
super().__init__(walker)
class StatusLinks(urwid.ListBox):
"""Shows status links."""
def __init__(self, links):
def widget(url, title):
return Button(title or url, on_press=lambda btn: webbrowser.open(url))
walker = urwid.SimpleFocusListWalker(
[widget(url, title) for url, title in links]
)
super().__init__(walker)
class ExceptionStackTrace(urwid.ListBox):
"""Shows an exception stack trace."""
def __init__(self, ex):
@ -132,6 +146,7 @@ class Help(urwid.Padding):
yield urwid.Text(h(" [R] - Reply to current status"))
yield urwid.Text(h(" [S] - Show text marked as sensitive"))
yield urwid.Text(h(" [T] - Show status thread (replies)"))
yield urwid.Text(h(" [L] - Show the status links"))
yield urwid.Text(h(" [U] - Show the status data in JSON as received from the server"))
yield urwid.Text(h(" [V] - Open status in default browser"))
yield urwid.Divider()

View File

@ -26,6 +26,7 @@ class Timeline(urwid.Columns):
"reblog", # Reblog status
"reply", # Compose a reply to a status
"source", # Show status source
"links", # Show status links
"thread", # Show thread for status
]
@ -140,6 +141,10 @@ class Timeline(urwid.Columns):
self.refresh_status_details()
return
if key in ("l", "L"):
self._emit("links", status)
return
if key in ("t", "T"):
self._emit("thread", status)
return
@ -281,6 +286,7 @@ class StatusDetails(urwid.Pile):
"[F]avourite",
"[V]iew",
"[T]hread" if not self.in_thread else "",
"[L]inks",
"[R]eply",
"So[u]rce",
"[H]elp",

View File

@ -1,3 +1,4 @@
from html.parser import HTMLParser
import re
import shutil
import subprocess
@ -74,3 +75,30 @@ def show_media(paths):
raise Exception("Cannot find an image viewer")
subprocess.run([viewer] + paths)
class LinkParser(HTMLParser):
def reset(self):
super().reset()
self.links = []
def handle_starttag(self, tag, attrs):
if tag == "a":
href, title = None, None
for name, value in attrs:
if name == "href":
href = value
if name == "title":
title = value
if href:
self.links.append((href, title))
def parse_content_links(content):
"""Parse <a> tags from status's `content` and return them as a list of
(href, title), where `title` may be None.
"""
parser = LinkParser()
parser.feed(content)
return parser.links[:]