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 .compose import StatusComposer
from .constants import PALETTE from .constants import PALETTE
from .entities import Status from .entities import Status
from .overlays import ExceptionStackTrace, GotoMenu, Help, StatusSource from .overlays import ExceptionStackTrace, GotoMenu, Help, StatusSource, StatusLinks
from .overlays import StatusDeleteConfirmation from .overlays import StatusDeleteConfirmation
from .timeline import Timeline from .timeline import Timeline
from .utils import show_media from .utils import parse_content_links, show_media
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -182,6 +182,9 @@ class TUI(urwid.Frame):
def _source(timeline, status): def _source(timeline, status):
self.show_status_source(status) self.show_status_source(status)
def _links(timeline, status):
self.show_links(status)
def _media(timeline, status): def _media(timeline, status):
self.show_media(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, "reblog", self.async_toggle_reblog)
urwid.connect_signal(timeline, "reply", _reply) urwid.connect_signal(timeline, "reply", _reply)
urwid.connect_signal(timeline, "source", _source) urwid.connect_signal(timeline, "source", _source)
urwid.connect_signal(timeline, "links", _links)
def build_timeline(self, name, statuses): def build_timeline(self, name, statuses):
def _close(*args): def _close(*args):
@ -302,6 +306,14 @@ class TUI(urwid.Frame):
title="Status source", 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): def show_exception(self, exception):
self.open_overlay( self.open_overlay(
widget=ExceptionStackTrace(exception), widget=ExceptionStackTrace(exception),

View File

@ -20,6 +20,20 @@ class StatusSource(urwid.ListBox):
super().__init__(walker) 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): class ExceptionStackTrace(urwid.ListBox):
"""Shows an exception stack trace.""" """Shows an exception stack trace."""
def __init__(self, ex): 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(" [R] - Reply to current status"))
yield urwid.Text(h(" [S] - Show text marked as sensitive")) yield urwid.Text(h(" [S] - Show text marked as sensitive"))
yield urwid.Text(h(" [T] - Show status thread (replies)")) 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(" [U] - Show the status data in JSON as received from the server"))
yield urwid.Text(h(" [V] - Open status in default browser")) yield urwid.Text(h(" [V] - Open status in default browser"))
yield urwid.Divider() yield urwid.Divider()

View File

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

View File

@ -1,3 +1,4 @@
from html.parser import HTMLParser
import re import re
import shutil import shutil
import subprocess import subprocess
@ -74,3 +75,30 @@ def show_media(paths):
raise Exception("Cannot find an image viewer") raise Exception("Cannot find an image viewer")
subprocess.run([viewer] + paths) 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[:]