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 ...@@ -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 `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 client or reference implementation <https://github.com/buckket/twtxt/>`_ by the
original inventor of twtxt. ``tt`` only serves as a viewer of the twtxt original inventor of twtxt. ``tt`` only serves as a viewer of the twtxt
conversations, a `twtxt.net <https://twtxt.net/>`_ extension. It is not conversations, a `twtxt.net <https://twtxt.net/>`_ extension. It is also
possible to post twts with it yet. The heavy lifting is still done by the possible to post twts or replies. The heavy lifting is still done by the
reference implementation, especially fetching and storing the *twtxt.txt* files reference implementation, especially fetching and storing the *twtxt.txt* files
in a local cache. ``tt`` just uses ``twtxt``'s *~/.config/twtxt/cache.db* for 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 all the twts to show. In a second cache file next to it – highly creatively
...@@ -66,6 +66,7 @@ Features ...@@ -66,6 +66,7 @@ Features
* Open and copy links in URL view. * Open and copy links in URL view.
* Folding of mentions and twtxt subject hashes. * Folding of mentions and twtxt subject hashes.
* Subject hashes can be hidden. * Subject hashes can be hidden.
* Compose new or reply twts.
TODO TODO
==== ====
...@@ -86,7 +87,6 @@ TODO ...@@ -86,7 +87,6 @@ TODO
* Ability to show the txt's raw text. * Ability to show the txt's raw text.
* Manage subscriptions. * Manage subscriptions.
* Reload feeds. * Reload feeds.
* Compose twts.
WTF! WTF!
==== ====
......
#!/usr/bin/python3 #!/usr/bin/python3
# encoding: utf-8 # encoding: utf-8
import datetime
import dateutil.parser
import subprocess import subprocess
import twtxtmanager import twtxtmanager
import twtxtparser import twtxtparser
...@@ -185,15 +187,27 @@ class TwtsListBox(widgets.VimListBox): ...@@ -185,15 +187,27 @@ class TwtsListBox(widgets.VimListBox):
if key == "q" and self._search: if key == "q" and self._search:
self.clear_search() self.clear_search()
return return
if key == "t":
self.open_new_twt_form()
return
if key == "a":
self.open_reply_twt_form()
return
return super().keypress(size, key) return super().keypress(size, key)
def toggle_read(self): @property
def selected_twt(self):
pos = self.focus_position 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) self._manager.toggle_read(twt)
pos = self.focus_position
row = self.body[pos] row = self.body[pos]
row.set_attr_map(twtxtrenderer.read_normal_map if twt.read else twtxtrenderer.unread_normal_map) 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) row.set_focus_map(twtxtrenderer.read_focus_map if twt.read else twtxtrenderer.unread_focus_map)
...@@ -201,8 +215,7 @@ class TwtsListBox(widgets.VimListBox): ...@@ -201,8 +215,7 @@ class TwtsListBox(widgets.VimListBox):
def list_urls(self): def list_urls(self):
twt = self._twts[self.focus_position] frame.body.push_widget(URLListBox(self._manager, self.selected_twt))
frame.body.push_widget(URLListBox(self._manager, twt))
def _loop_twts(self, downwards=True): def _loop_twts(self, downwards=True):
...@@ -264,6 +277,14 @@ class TwtsListBox(widgets.VimListBox): ...@@ -264,6 +277,14 @@ class TwtsListBox(widgets.VimListBox):
self._search = None 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): class URLListBox(widgets.VimListBox):
CONV_URL = "https://twtxt.net/conv/%s" CONV_URL = "https://twtxt.net/conv/%s"
...@@ -401,6 +422,55 @@ class URLListBox(widgets.VimListBox): ...@@ -401,6 +422,55 @@ class URLListBox(widgets.VimListBox):
subprocess.run(["twtxt", "follow", token.nick, token.url]) 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 show_subjects = False
fold_subjects = True fold_subjects = True
fold_mentions = True fold_mentions = True
......
...@@ -11,6 +11,7 @@ import twtxtparser ...@@ -11,6 +11,7 @@ import twtxtparser
class TwtxtManager: class TwtxtManager:
TWTXT_TXT = '/home/lyse/.config/twtxt/twtxt.txt'
def __init__(self): def __init__(self):
self._cache = None self._cache = None
...@@ -37,10 +38,10 @@ class TwtxtManager: ...@@ -37,10 +38,10 @@ class TwtxtManager:
self.read_twts_count = 0 self.read_twts_count = 0
self.unread_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') self._cache = shelve.open('/home/lyse/.config/twtxt/cache')
own_source = twtxt.models.Source(nick="lyse", url="https://lyse.isobeef.org/twtxt.txt") 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) own_twts = twtxt.parser.parse_tweets(fd.readlines(), own_source)
self._cache[own_source.url] = {'tweets': own_twts} self._cache[own_source.url] = {'tweets': own_twts}
self._cache2 = shelve.open('/home/lyse/.config/twtxt/cache2') self._cache2 = shelve.open('/home/lyse/.config/twtxt/cache2')
...@@ -184,3 +185,13 @@ class TwtxtManager: ...@@ -184,3 +185,13 @@ class TwtxtManager:
return False 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): ...@@ -27,6 +27,16 @@ class SelectableAttrMap(urwid.AttrMap):
return key 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): class TextEdit(urwid.Edit):
""" """
More sophisticated urwid.Edit widget which has support for very common More sophisticated urwid.Edit widget which has support for very common
...@@ -44,17 +54,23 @@ class TextEdit(urwid.Edit): ...@@ -44,17 +54,23 @@ class TextEdit(urwid.Edit):
key = "right" key = "right"
elif key == "ctrl left": elif key == "ctrl left":
self.move_to_last_word() self.move_to_last_word()
return
elif key == "ctrl right": elif key == "ctrl right":
self.move_to_next_word() self.move_to_next_word()
return
elif key == "ctrl w": elif key == "ctrl w":
self.delete_last_word() self.delete_last_word()
return
elif key == "ctrl k": elif key == "ctrl k":
self.delete_to_end() self.delete_to_end()
return
elif key == "meta d": elif key == "meta d":
self.delete_next_word() self.delete_next_word()
return
elif key == "ctrl u": elif key == "ctrl u":
self.delete_to_home() self.delete_to_home()
super().keypress(size, key) return
return super().keypress(size, key)
def _get_last_word_pos(self): 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