Added client-side thread pinning.

themes
desvox 2018-07-27 22:57:48 -05:00
parent 8044468f8b
commit f5b06cca18
1 changed files with 139 additions and 57 deletions

View File

@ -220,7 +220,7 @@ default_prefs = {
} }
bars = { bars = {
"index": "[RET]Open [/]Search [C]ompose [R]efresh [O]ptions [?]Help [Q]uit", "index": "[RET]Open [/]Search [C]ompose [R]efresh [*]Bookmark [O]ptions [?]Help [Q]uit",
"thread": "[Q]Back [RET]Menu [C]ompose [^R]eply [R]efresh [0-9]Goto [B/T]End [</>]Jump[X]%d [/]Search" "thread": "[Q]Back [RET]Menu [C]ompose [^R]eply [R]efresh [0-9]Goto [B/T]End [</>]Jump[X]%d [/]Search"
} }
@ -263,6 +263,7 @@ escape_map = {
rcpath = os.path.join(os.getenv("HOME"), ".bbjrc") rcpath = os.path.join(os.getenv("HOME"), ".bbjrc")
markpath = os.path.join(os.getenv("HOME"), ".bbjmarks") markpath = os.path.join(os.getenv("HOME"), ".bbjmarks")
pinpath = os.path.join(os.getenv("HOME"), ".bbjpins")
class App(object): class App(object):
def __init__(self): def __init__(self):
@ -272,8 +273,9 @@ class App(object):
self.thread = None self.thread = None
self.usermap = {} self.usermap = {}
self.window_split = False self.window_split = False
self.last_pos = None self.last_index_pos = None
self.last_alarm = None self.last_alarm = None
self.client_pinned_threads = load_client_pins()
self.match_data = { self.match_data = {
"query": "", "query": "",
"matches": [], "matches": [],
@ -622,12 +624,17 @@ class App(object):
return [value_type(q) for q in quotes] return [value_type(q) for q in quotes]
def make_thread_body(self, thread): def make_thread_body(self, thread, pinned=False):
""" """
Returns the pile widget that comprises a thread in the index. Returns the pile widget that comprises a thread in the index.
""" """
button = cute_button(">>", self.thread_load, thread["thread_id"]) button = cute_button(">>", self.thread_load, thread["thread_id"])
title = urwid.Text(thread["title"]) if pinned == "server":
title = urwid.AttrWrap(urwid.Text("[STICKY] " + thread["title"]), "20")
elif pinned == "client":
title = urwid.AttrWrap(urwid.Text("[*] " + thread["title"]), "50")
else:
title = urwid.Text(thread["title"])
user = self.usermap[thread["author"]] user = self.usermap[thread["author"]]
dateline = [ dateline = [
("default", "by "), ("default", "by "),
@ -662,7 +669,9 @@ class App(object):
def make_message_body(self, message, no_action=False): def make_message_body(self, message, no_action=False):
""" """
Returns the widgets that comprise a message in a thread, including the Returns the widgets that comprise a message in a thread, including the
text body, author info and the action button text body, author info and the action button. Unlike the thread objects
used in the index, this is not a pile widget, because using a pile
causes line-by-line text scrolling to be unusable.
""" """
info = "@ " + self.timestring(message["created"]) info = "@ " + self.timestring(message["created"])
if message["edited"]: if message["edited"]:
@ -726,28 +735,72 @@ class App(object):
self.mode = "index" self.mode = "index"
self.thread = None self.thread = None
self.window_split = False self.window_split = False
if not threads: if threads:
# passing in an argument for threads implies that we are showing a
# narrowed selection of content, so we dont want to resume last_index_pos
self.last_index_pos = False
else:
threads, usermap = network.thread_index() threads, usermap = network.thread_index()
self.usermap.update(usermap) self.usermap.update(usermap)
else:
self.last_pos = False
self.walker.clear() self.walker.clear()
try: server_pin_counter = 0
target_id, pos = self.last_pos client_pin_counter = 0
except:
target_id = pos = 0 for thread in threads:
if thread["pinned"]:
self.walker.insert(server_pin_counter, self.make_thread_body(thread, pinned="server"))
server_pin_counter += 1
elif thread["thread_id"] in self.client_pinned_threads:
self.walker.insert(client_pin_counter, self.make_thread_body(thread, pinned="client"))
client_pin_counter += 1
else:
self.walker.append(self.make_thread_body(thread))
for index, thread in enumerate(threads):
self.walker.append(self.make_thread_body(thread))
if thread["thread_id"] == target_id:
pos = index
self.set_bars(True) self.set_bars(True)
try:
self.box.change_focus(self.loop.screen_size, pos) if self.last_index_pos:
self.box.set_focus_valign("middle") thread_ids = [widget.thread["thread_id"] for widget in self.walker]
except: if self.last_index_pos in thread_ids:
pass self.box.change_focus(self.loop.screen_size, thread_ids.index(self.last_index_pos))
self.box.set_focus_valign("middle")
elif self.walker:
# checks to make sure there are any posts to focus
self.box.set_focus(0)
def thread_load(self, button, thread_id):
"""
Open a thread.
"""
if app.mode == "index":
# make sure the index goes back to this selected thread
pos = self.get_focus_post(return_widget=True)
self.last_index_pos = pos.thread["thread_id"]
if not self.window_split:
self.body.attr_map = {None: "default"}
self.mode = "thread"
thread, usermap = network.thread_load(thread_id, format="sequential")
self.usermap.update(usermap)
self.thread = thread
self.match_data["matches"].clear()
self.walker.clear()
for message in thread["messages"]:
self.walker += self.make_message_body(message)
self.set_default_header()
self.set_default_footer()
self.goto_post(mark(thread_id))
def toggle_client_pin(self):
if self.mode != "index":
return
thread_id = self.walker.get_focus()[0].thread["thread_id"]
self.client_pinned_threads = toggle_client_pin(thread_id)
self.index()
def search_index_callback(self, query): def search_index_callback(self, query):
@ -847,38 +900,19 @@ class App(object):
width=("relative", 40), height=6) width=("relative", 40), height=6)
def thread_load(self, button, thread_id):
"""
Open a thread.
"""
if app.mode == "index":
pos = app.get_focus_post()
self.last_pos = (self.walker[pos].thread["thread_id"], pos)
if not self.window_split:
self.body.attr_map = {None: "default"}
self.mode = "thread"
thread, usermap = network.thread_load(thread_id, format="sequential")
self.usermap.update(usermap)
self.thread = thread
self.match_data["matches"].clear()
self.walker.clear()
for message in thread["messages"]:
self.walker += self.make_message_body(message)
self.set_default_header()
self.set_default_footer()
self.goto_post(mark(thread_id))
def refresh(self): def refresh(self):
self.remove_overlays() self.remove_overlays()
if self.mode == "index": if self.mode == "index":
return self.index() # check to make sure there are any posts
mark() if self.walker:
thread = self.thread["thread_id"] self.last_index_pos = self.get_focus_post(True).thread["thread_id"]
self.thread_load(None, thread) self.index()
self.goto_post(mark(thread)) else:
mark()
thread = self.thread["thread_id"]
self.thread_load(None, thread)
self.goto_post(mark(thread))
self.temp_footer_message("Refreshed content!")
def back(self, terminate=False): def back(self, terminate=False):
@ -916,11 +950,11 @@ class App(object):
self.index() self.index()
def get_focus_post(self): def get_focus_post(self, return_widget=False):
pos = self.box.get_focus_path()[0] pos = self.box.get_focus_path()[0]
if self.mode == "thread": if self.mode == "thread":
return (pos - (pos % 5)) // 5 return (pos - (pos % 5)) // 5
return pos return pos if not return_widget else self.walker[pos]
def header_jump_next(self): def header_jump_next(self):
@ -1831,15 +1865,17 @@ class ExternalEditor(urwid.Terminal):
if body and not re.search("^>>[0-9]+$", body): if body and not re.search("^>>[0-9]+$", body):
self.params.update({"body": body}) self.params.update({"body": body})
network.request(self.endpoint, **self.params) network.request(self.endpoint, **self.params)
app.refresh()
if self.endpoint == "edit_post": if self.endpoint == "edit_post":
app.refresh()
app.goto_post(self.params["post_id"]) app.goto_post(self.params["post_id"])
elif app.mode == "thread": elif app.mode == "thread":
app.refresh()
app.goto_post(app.thread["reply_count"]) app.goto_post(app.thread["reply_count"])
else: else:
app.box.keypress(app.loop.screen_size, "t") app.last_pos = None
app.index()
else: else:
app.temp_footer_message("EMPTY POST DISCARDED") app.temp_footer_message("EMPTY POST DISCARDED")
@ -1984,11 +2020,13 @@ class ActionBox(urwid.ListBox):
app.back(keyl == "q") app.back(keyl == "q")
elif keyl == "b": elif keyl == "b":
offset = 5 if (app.mode == "thread") else 1 if app.walker:
self.change_focus(size, len(self.body) - offset) offset = 5 if (app.mode == "thread") else 1
self.change_focus(size, len(self.body) - offset)
elif keyl == "t": elif keyl == "t":
self.change_focus(size, 0) if app.walker:
self.change_focus(size, 0)
elif key == "ctrl l": elif key == "ctrl l":
wipe_screen() wipe_screen()
@ -2029,6 +2067,9 @@ class ActionBox(urwid.ListBox):
elif key == "@": elif key == "@":
app.do_search_result(False) app.do_search_result(False)
elif key == "*":
app.toggle_client_pin()
elif key == "~": elif key == "~":
# sssssshhhhhhhh # sssssshhhhhhhh
app.loop.stop() app.loop.stop()
@ -2038,6 +2079,18 @@ class ActionBox(urwid.ListBox):
elif keyl == "f12": elif keyl == "f12":
app.loop.stop() app.loop.stop()
call("clear", shell=True)
try:
line = input("(REPL)> ")
while line:
try:
print(eval(line))
except BaseException as E:
print(E)
line = input("(REPL)> ")
except EOFError:
pass
app.loop.start()
elif app.mode == "thread" and not app.window_split and not overlay: elif app.mode == "thread" and not app.window_split and not overlay:
message = app.thread["messages"][app.get_focus_post()] message = app.thread["messages"][app.get_focus_post()]
@ -2258,6 +2311,8 @@ def frame_theme(mode="default"):
def bbjrc(mode, **params): def bbjrc(mode, **params):
# TODO: Refactor this, the arguments and code do not properly match how this
# function is used anymore
""" """
Maintains a user a preferences file, setting or returning Maintains a user a preferences file, setting or returning
values depending on `mode`. values depending on `mode`.
@ -2312,6 +2367,33 @@ def mark(directive=True):
return 0 return 0
def load_client_pins():
"""
Retrieve the pinned threads list.
"""
try:
with open(pinpath, "r") as _in:
pins = json.load(_in)
except FileNotFoundError:
pins = []
return pins
def toggle_client_pin(thread_id):
"""
Given a thread_id, will either add it to the pins or remove it from the
pins if it already exists.
"""
pins = load_client_pins()
if thread_id in pins:
pins.remove(thread_id)
else:
pins.append(thread_id)
with open(pinpath, "w") as _out:
json.dump(pins, _out)
return pins
def ignore(*_, **__): def ignore(*_, **__):
""" """
The blackness of my soul. The blackness of my soul.