Commit 05abee74 authored by Lysander Trischler's avatar Lysander Trischler
Browse files

Add twt publish form

parent 42286c43
......@@ -13,8 +13,8 @@ At the moment it is a horribly hacked together piece of software that relies on
`my patched version <https://github.com/der-lyse/twtxt-1>`_ of the `twtxt CLI
client or reference implementation <https://github.com/buckket/twtxt/>`_ by the
original inventor of twtxt. ``tt`` only serves as a viewer of the twtxt
conversations, a `twtxt.net <https://twtxt.net/>`_ extension. It is not
possible to post twts with it yet. The heavy lifting is still done by the
conversations, a `twtxt.net <https://twtxt.net/>`_ extension. It is also
possible to post twts or replies. The heavy lifting is still done by the
reference implementation, especially fetching and storing the *twtxt.txt* files
in a local cache. ``tt`` just uses ``twtxt``'s *~/.config/twtxt/cache.db* for
all the twts to show. In a second cache file next to it – highly creatively
......@@ -66,6 +66,7 @@ Features
* Open and copy links in URL view.
* Folding of mentions and twtxt subject hashes.
* Subject hashes can be hidden.
* Compose new or reply twts.
TODO
====
......@@ -86,7 +87,6 @@ TODO
* Ability to show the txt's raw text.
* Manage subscriptions.
* Reload feeds.
* Compose twts.
WTF!
====
......
#!/usr/bin/python3
# encoding: utf-8
import datetime
import dateutil.parser
import subprocess
import twtxtmanager
import twtxtparser
......@@ -185,15 +187,27 @@ class TwtsListBox(widgets.VimListBox):
if key == "q" and self._search:
self.clear_search()
return
if key == "t":
self.open_new_twt_form()
return
if key == "a":
self.open_reply_twt_form()
return
return super().keypress(size, key)
def toggle_read(self):
@property
def selected_twt(self):
pos = self.focus_position
twt = self._twts[pos]
return self._twts[pos]
def toggle_read(self):
twt = self.selected_twt
self._manager.toggle_read(twt)
pos = self.focus_position
row = self.body[pos]
row.set_attr_map(twtxtrenderer.read_normal_map if twt.read else twtxtrenderer.unread_normal_map)
row.set_focus_map(twtxtrenderer.read_focus_map if twt.read else twtxtrenderer.unread_focus_map)
......@@ -201,8 +215,7 @@ class TwtsListBox(widgets.VimListBox):
def list_urls(self):
twt = self._twts[self.focus_position]
frame.body.push_widget(URLListBox(self._manager, twt))
frame.body.push_widget(URLListBox(self._manager, self.selected_twt))
def _loop_twts(self, downwards=True):
......@@ -264,6 +277,14 @@ class TwtsListBox(widgets.VimListBox):
self._search = None
def open_new_twt_form(self):
frame.body.push_widget(TwtForm(self._manager))
def open_reply_twt_form(self):
frame.body.push_widget(TwtForm(self._manager, self.selected_twt, [self.body[self.focus_position]]))
class URLListBox(widgets.VimListBox):
CONV_URL = "https://twtxt.net/conv/%s"
......@@ -401,6 +422,55 @@ class URLListBox(widgets.VimListBox):
subprocess.run(["twtxt", "follow", token.nick, token.url])
class TwtForm(urwid.ListBox):
def __init__(self, manager, original_twt=None, rendered_conversation=[]):
if original_twt:
authors = "".join(["@<%s %s> " % (a.nick, a.url)
for a in self._collect_authors(original_twt)])
subject = "(#<%s %s>) " % (original_twt.hash,
"https://twtxt.net/conv/%s" % original_twt.hash)
text = authors + subject
else:
text = ""
self._text_edit = widgets.TextEdit(edit_text=text)
self._created_at_edit = widgets.TextEdit(edit_text=datetime.datetime.now(datetime.timezone.utc)
.replace(second=0,
microsecond=0)
.astimezone()
.isoformat())
publish = urwid.Button("Publish Twt", on_press=self.publish_twt)
cancel = urwid.Button("Cancel", on_press=lambda *_: frame.body.pop_widget())
super().__init__(list(map(widgets.Unselectable, rendered_conversation)) + [
urwid.LineBox(self._text_edit, title="Reply" if original_twt else "New Twt", title_align="left"),
urwid.LineBox(self._created_at_edit, title="Created at", title_align="left"),
urwid.Columns([urwid.Text(""),
(len(publish.label) + 4, publish),
(len(cancel.label) + 4, cancel)],
dividechars=2)])
self._manager = manager
def _collect_authors(self, twt):
authors = [twt.source]
for reply in twt.replies:
for reply_author in self._collect_authors(reply):
if reply_author not in authors:
authors.append(reply_author)
return authors
def publish_twt(self, *_):
created_at = dateutil.parser.parse(self._created_at_edit.edit_text)
if not created_at.tzinfo:
created_at = created_at.replace(tzinfo=datetime.timezone.utc)
text = self._text_edit.edit_text
self._manager.publish_twt(created_at, text)
frame.body.pop_widget()
show_subjects = False
fold_subjects = True
fold_mentions = True
......
......@@ -11,6 +11,7 @@ import twtxtparser
class TwtxtManager:
TWTXT_TXT = '/home/lyse/.config/twtxt/twtxt.txt'
def __init__(self):
self._cache = None
......@@ -37,10 +38,10 @@ class TwtxtManager:
self.read_twts_count = 0
self.unread_twts_count = 0
MAX_TWEETS_LIMIT = 500
MAX_TWEETS_LIMIT = 2000
self._cache = shelve.open('/home/lyse/.config/twtxt/cache')
own_source = twtxt.models.Source(nick="lyse", url="https://lyse.isobeef.org/twtxt.txt")
with open('/home/lyse/.config/twtxt/twtxt.txt', 'r', encoding='utf-8') as fd:
with open(self.TWTXT_TXT, 'r', encoding='utf-8') as fd:
own_twts = twtxt.parser.parse_tweets(fd.readlines(), own_source)
self._cache[own_source.url] = {'tweets': own_twts}
self._cache2 = shelve.open('/home/lyse/.config/twtxt/cache2')
......@@ -184,3 +185,13 @@ class TwtxtManager:
return False
def publish_twt(self, created_at, text):
"""
Publish the given text at the specified timestamp by writing it to the
local twtxt.txt file.
"""
with open(self.TWTXT_TXT, 'a', encoding='utf-8') as fd:
fd.write("%s\t%s\n" % (created_at.isoformat(), text))
......@@ -27,6 +27,16 @@ class SelectableAttrMap(urwid.AttrMap):
return key
class Unselectable(urwid.WidgetWrap):
"""
Just a wrapper around a selectable widget which should not be focusable
anymore.
"""
def selectable(self):
return False
class TextEdit(urwid.Edit):
"""
More sophisticated urwid.Edit widget which has support for very common
......@@ -44,17 +54,23 @@ class TextEdit(urwid.Edit):
key = "right"
elif key == "ctrl left":
self.move_to_last_word()
return
elif key == "ctrl right":
self.move_to_next_word()
return
elif key == "ctrl w":
self.delete_last_word()
return
elif key == "ctrl k":
self.delete_to_end()
return
elif key == "meta d":
self.delete_next_word()
return
elif key == "ctrl u":
self.delete_to_home()
super().keypress(size, key)
return
return super().keypress(size, key)
def _get_last_word_pos(self):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment