Public beta, code is becoming a dumpsterfire T_T
parent
b731ab69fa
commit
b1bd03ec8e
|
@ -421,3 +421,28 @@ class BBJ(object):
|
|||
"""
|
||||
response = self("thread_load", format=format, thread_id=thread_id)
|
||||
return response["data"], response["usermap"]
|
||||
|
||||
|
||||
def edit_query(self, thread_id, post_id):
|
||||
"""
|
||||
Queries ther server database to see if a post can
|
||||
be edited by the logged in user. thread_id and
|
||||
post_id are required.
|
||||
|
||||
Returns a message object on success, or raises
|
||||
a UserWarning describing why it failed.
|
||||
"""
|
||||
response = self("edit_query", thread_id=thread_id, post_id=int(post_id))
|
||||
return response["data"]
|
||||
|
||||
|
||||
def can_edit(self, thread_id, post_id):
|
||||
"""
|
||||
Return bool True/False that the post at thread_id | post_id
|
||||
can be edited by the logged in user. Will not raise UserWarning.
|
||||
"""
|
||||
try:
|
||||
result = bool(self.edit_query(thread_id, post_id))
|
||||
except UserWarning:
|
||||
result = False
|
||||
return result
|
||||
|
|
|
@ -95,7 +95,7 @@ class App(object):
|
|||
("default", "default", "default"),
|
||||
("bar", "light magenta", "default"),
|
||||
("button", "light red", "default"),
|
||||
("quote", "light green,underline", "default"),
|
||||
("quote", "brown", "default"),
|
||||
("opt_prompt", "black", "light gray"),
|
||||
("opt_header", "light cyan", "default"),
|
||||
("hover", "light cyan", "default"),
|
||||
|
@ -111,7 +111,15 @@ class App(object):
|
|||
("3", "dark green", "default"),
|
||||
("4", "dark blue", "default"),
|
||||
("5", "dark cyan", "default"),
|
||||
("6", "dark magenta", "default")
|
||||
("6", "dark magenta", "default"),
|
||||
|
||||
# and have the bolded colors use the same values times 10
|
||||
("10", "light red", "default"),
|
||||
("20", "yellow", "default"),
|
||||
("30", "light green", "default"),
|
||||
("40", "light blue", "default"),
|
||||
("50", "light cyan", "default"),
|
||||
("60", "light magenta", "default")
|
||||
]
|
||||
|
||||
self.mode = None
|
||||
|
@ -120,11 +128,12 @@ class App(object):
|
|||
self.prefs = bbjrc("load")
|
||||
self.window_split = False
|
||||
self.last_pos = 0
|
||||
|
||||
# self.jump_stack = []
|
||||
self.walker = urwid.SimpleFocusListWalker([])
|
||||
self.box = ActionBox(self.walker)
|
||||
self.loop = urwid.MainLoop(urwid.Frame(
|
||||
urwid.LineBox(
|
||||
ActionBox(self.walker),
|
||||
self.box,
|
||||
title=self.prefs["frame_title"],
|
||||
**frame_theme()
|
||||
)), colors)
|
||||
|
@ -244,6 +253,152 @@ class App(object):
|
|||
return "less than a minute ago"
|
||||
|
||||
|
||||
def quote_view_action(self, button, post_id):
|
||||
message = self.thread["messages"][post_id]
|
||||
author = self.usermap[message["author"]]
|
||||
color = str(author["color"])
|
||||
pid_string = ">>%d" % post_id
|
||||
title = [
|
||||
("button", pid_string),
|
||||
("default", " by "),
|
||||
(color, "~" + author["user_name"])
|
||||
]
|
||||
widget = OptionsMenu(
|
||||
urwid.ListBox(
|
||||
urwid.SimpleFocusListWalker([
|
||||
*self.make_message_body(message)
|
||||
])),
|
||||
title=pid_string,
|
||||
**frame_theme()
|
||||
)
|
||||
|
||||
self.loop.widget = urwid.Overlay(
|
||||
widget, self.loop.widget,
|
||||
align=("relative", 50),
|
||||
valign=("relative", 50),
|
||||
width=("relative", 98),
|
||||
height=("relative", 60)
|
||||
)
|
||||
|
||||
|
||||
|
||||
def quote_view_menu(self, button, post_ids):
|
||||
if len(post_ids) == 1:
|
||||
return self.quote_view_action(button, post_ids[0])
|
||||
# else:
|
||||
# self.loop.widget = self.loop.widget[0]
|
||||
buttons = []
|
||||
for pid in post_ids:
|
||||
try:
|
||||
message = self.thread["messages"][pid]
|
||||
author = self.usermap[message["author"]]
|
||||
color = str(author["color"])
|
||||
label = [
|
||||
("button", ">>%d " % pid),
|
||||
"(", (color, author["user_name"]), ")"]
|
||||
except:
|
||||
label = ("button", ">>%d")
|
||||
buttons.append(cute_button(label, self.quote_view_action, pid))
|
||||
|
||||
widget = OptionsMenu(
|
||||
urwid.ListBox(urwid.SimpleFocusListWalker(buttons)),
|
||||
title="View a Quote",
|
||||
**frame_theme()
|
||||
)
|
||||
|
||||
self.loop.widget = urwid.Overlay(
|
||||
widget, self.loop.widget,
|
||||
align=("relative", 50),
|
||||
valign=("relative", 50),
|
||||
width=30,
|
||||
height=len(buttons) + 3
|
||||
)
|
||||
|
||||
|
||||
def remove_overlays(self):
|
||||
while True:
|
||||
try: self.loop.widget = self.loop.widget[0]
|
||||
except: break
|
||||
|
||||
|
||||
def edit_post(self, button, message):
|
||||
post_id = message["post_id"]
|
||||
thread_id = message["thread_id"]
|
||||
# first we need to get the server's version of the message
|
||||
# instead of our formatted one
|
||||
try:
|
||||
message = network.edit_query(thread_id, post_id)
|
||||
except UserWarning as e:
|
||||
self.remove_overlays()
|
||||
return self.temp_footer_message(e.description)
|
||||
|
||||
self.loop.widget = urwid.Overlay(
|
||||
urwid.LineBox(
|
||||
ExternalEditor(
|
||||
"edit_post",
|
||||
init_body=message["body"],
|
||||
post_id=post_id,
|
||||
thread_id=thread_id),
|
||||
title="[F1]Abort (save/quit to commit)",
|
||||
**frame_theme()),
|
||||
self.loop.widget,
|
||||
align="center",
|
||||
valign="middle",
|
||||
width=("relative", 75),
|
||||
height=("relative", 75))
|
||||
|
||||
|
||||
def reply(self, button, message):
|
||||
self.remove_overlays()
|
||||
self.compose(init_body=">>%d\n\n" % message["post_id"])
|
||||
|
||||
|
||||
|
||||
def on_post(self, button, message):
|
||||
quotes = self.get_quotes(message)
|
||||
author = self.usermap[message["author"]]
|
||||
buttons = [
|
||||
urwid.Button("Reply", self.reply, message),
|
||||
]
|
||||
|
||||
if quotes and message["post_id"] != 0:
|
||||
buttons.insert(1, urwid.Button(
|
||||
"View %sQuote" % ("a " if len(quotes) != 1 else ""),
|
||||
self.quote_view_menu, quotes))
|
||||
|
||||
if network.can_edit(message["thread_id"], message["post_id"]):
|
||||
buttons.insert(0, urwid.Button("Edit Post", self.edit_post, message))
|
||||
|
||||
widget = OptionsMenu(
|
||||
urwid.ListBox(urwid.SimpleFocusListWalker(buttons)),
|
||||
title=str(">>%d (%s)" % (message["post_id"], author["user_name"])),
|
||||
**frame_theme()
|
||||
)
|
||||
size = self.loop.screen_size
|
||||
|
||||
self.loop.widget = urwid.Overlay(
|
||||
urwid.AttrMap(widget, str(author["color"]*10)),
|
||||
self.loop.widget,
|
||||
align=("relative", 50),
|
||||
valign=("relative", 50),
|
||||
width=30,
|
||||
height=len(buttons) + 3
|
||||
)
|
||||
|
||||
|
||||
def get_quotes(self, msg_object, value_type=int):
|
||||
"""
|
||||
Returns the post_ids that msg_object is quoting.
|
||||
Is a list, may be empty. ids are ints by default
|
||||
but can be passed `str` for strings.
|
||||
"""
|
||||
quotes = []
|
||||
for paragraph in msg_object["body"]:
|
||||
# yes python is lisp fuck you
|
||||
[quotes.append(cdr) for car, cdr in paragraph if car == "quote"]
|
||||
return [value_type(q) for q in quotes]
|
||||
|
||||
|
||||
def make_message_body(self, message):
|
||||
"""
|
||||
Returns the widgets that comprise a message in a thread, including the
|
||||
|
@ -256,9 +411,10 @@ class App(object):
|
|||
name = urwid.Text("~{}".format(self.usermap[message["author"]]["user_name"]))
|
||||
post = str(message["post_id"])
|
||||
head = urwid.Columns([
|
||||
(2 + len(post), urwid.AttrMap(cute_button(">" + post), "button", "hover")),
|
||||
(len(name._text) + 1,
|
||||
urwid.AttrMap(name, str(self.usermap[message["author"]]["color"]))),
|
||||
(2 + len(post), urwid.AttrMap(
|
||||
cute_button(">" + post, self.on_post, message), "button", "hover")),
|
||||
(len(name._text) + 1, urwid.AttrMap(
|
||||
name, str(self.usermap[message["author"]]["color"]))),
|
||||
urwid.AttrMap(urwid.Text(info), "dim")
|
||||
])
|
||||
|
||||
|
@ -266,7 +422,9 @@ class App(object):
|
|||
return [
|
||||
head,
|
||||
urwid.Divider(),
|
||||
urwid.Columns([(self.prefs["max_text_width"], MessageBody(message["body"]))]),
|
||||
urwid.Columns([
|
||||
(self.prefs["max_text_width"], MessageBody(message))
|
||||
]),
|
||||
urwid.Divider(),
|
||||
urwid.AttrMap(urwid.Divider("-"), "dim")
|
||||
]
|
||||
|
@ -351,6 +509,7 @@ class App(object):
|
|||
|
||||
|
||||
def refresh(self, bottom=False):
|
||||
self.remove_overlays()
|
||||
if self.mode == "index":
|
||||
return self.index()
|
||||
self.thread_load(None, self.thread["thread_id"])
|
||||
|
@ -518,6 +677,9 @@ class App(object):
|
|||
urwid.Button("Change password", on_press=self.change_password),
|
||||
urwid.Divider(),
|
||||
urwid.Text(("button", "Your color:")),
|
||||
urwid.Text(("default", "This color will show on your "
|
||||
"post headers and when people quote you.")),
|
||||
urwid.Divider(),
|
||||
*user_colors
|
||||
]
|
||||
else:
|
||||
|
@ -651,14 +813,14 @@ class App(object):
|
|||
self.loop.stop()
|
||||
descriptor, path = tempfile.mkstemp()
|
||||
run("%s %s" % (self.prefs["editor"], path), shell=True)
|
||||
with open(descriptor) as _:
|
||||
with open(path) as _:
|
||||
body = _.read()
|
||||
os.remove(path)
|
||||
self.loop.start()
|
||||
return body.strip()
|
||||
|
||||
|
||||
def compose(self, title=None):
|
||||
def compose(self, title=None, init_body=""):
|
||||
"""
|
||||
Dispatches the appropriate composure mode and widget based on application
|
||||
context and user preferences.
|
||||
|
@ -710,7 +872,10 @@ class App(object):
|
|||
urwid.BoxAdapter(
|
||||
urwid.AttrMap(
|
||||
urwid.LineBox(
|
||||
ExternalEditor("thread_reply", thread_id=self.thread["thread_id"]),
|
||||
ExternalEditor(
|
||||
"thread_reply",
|
||||
init_body=init_body,
|
||||
thread_id=self.thread["thread_id"]),
|
||||
**frame_theme()
|
||||
),
|
||||
"bar"),
|
||||
|
@ -719,7 +884,11 @@ class App(object):
|
|||
|
||||
|
||||
class MessageBody(urwid.Text):
|
||||
def __init__(self, text_objects):
|
||||
"""
|
||||
An urwid.Text object that works with the BBJ formatting directives.
|
||||
"""
|
||||
def __init__(self, message):
|
||||
text_objects = message["body"]
|
||||
result = []
|
||||
last_directive = None
|
||||
for paragraph in text_objects:
|
||||
|
@ -738,7 +907,34 @@ class MessageBody(urwid.Text):
|
|||
result.append(("3", "%s\n" % body.strip()))
|
||||
|
||||
elif directive == "quote":
|
||||
result.append(("quote", ">>%s" % body))
|
||||
if message["post_id"] == 0:
|
||||
# Quotes in OP have no meaning, just insert them plainly
|
||||
result.append(("default", ">>%s" % body))
|
||||
continue
|
||||
elif body == "0":
|
||||
# quoting the OP, lets make it stand out a little
|
||||
result.append(("50", ">>OP"))
|
||||
continue
|
||||
|
||||
color = "2"
|
||||
try:
|
||||
# we can get this quote by its index in the thread
|
||||
message = app.thread["messages"][int(body)]
|
||||
user = app.usermap[message["author"]]
|
||||
# try to get the user's color, if its default use the normal one
|
||||
_c = user["color"]
|
||||
if _c != 0:
|
||||
color = str(_c)
|
||||
|
||||
if user != "anonymous" and user["user_name"] == network.user_name:
|
||||
display = "[You]"
|
||||
# bold it
|
||||
color += "0"
|
||||
else:
|
||||
display = "[%s]" % user["user_name"]
|
||||
except: # the quote may be garbage and refer to a nonexistant post
|
||||
display = ""
|
||||
result.append((color, ">>%s%s" % (body, display)))
|
||||
|
||||
elif directive == "rainbow":
|
||||
color = 1
|
||||
|
@ -751,9 +947,8 @@ class MessageBody(urwid.Text):
|
|||
else:
|
||||
result.append(("default", body))
|
||||
last_directive = directive
|
||||
|
||||
result.append("\n\n")
|
||||
result.pop()
|
||||
result.pop() # lazily ensure \n\n between paragraphs but not at the end
|
||||
super(MessageBody, self).__init__(result)
|
||||
|
||||
|
||||
|
@ -820,6 +1015,13 @@ class FootPrompt(Prompt):
|
|||
class ExternalEditor(urwid.Terminal):
|
||||
def __init__(self, endpoint, **params):
|
||||
self.file_descriptor, self.path = tempfile.mkstemp()
|
||||
with open(self.path, "w") as _:
|
||||
if params.get("init_body"):
|
||||
init_body = params.pop("init_body")
|
||||
else:
|
||||
init_body = ""
|
||||
_.write(init_body)
|
||||
|
||||
self.endpoint = endpoint
|
||||
self.params = params
|
||||
env = os.environ
|
||||
|
@ -836,7 +1038,7 @@ class ExternalEditor(urwid.Terminal):
|
|||
def keypress(self, size, key):
|
||||
if self.terminated:
|
||||
app.close_editor()
|
||||
with open(self.file_descriptor) as _:
|
||||
with open(self.path) as _:
|
||||
self.params.update({"body": _.read().strip()})
|
||||
os.remove(self.path)
|
||||
if self.params["body"]:
|
||||
|
|
|
@ -291,7 +291,7 @@ class API(object):
|
|||
(does not require a new body)
|
||||
|
||||
Returns the original message object without any formatting
|
||||
on success.
|
||||
on success. Returns a descriptive code 4 otherwise.
|
||||
"""
|
||||
if user == db.anon:
|
||||
raise BBJUserError("Anons cannot edit messages.")
|
||||
|
|
|
@ -1,22 +1,67 @@
|
|||
"""
|
||||
This module is not complete and none of its functions are currently
|
||||
used elsewhere. Subject to major refactoring.
|
||||
A B A N D O N ,:
|
||||
A L L H O P E ,' |
|
||||
/ :
|
||||
--' /
|
||||
F O R Y E W H O \/ /:/
|
||||
E N T E R H E R E / ://_\
|
||||
__/ /
|
||||
)'-. /
|
||||
Crude hacks lie beneath us. ./ :\
|
||||
/.' '
|
||||
This module includes a couple '/'
|
||||
of custom (GROAN) formatting +
|
||||
specifications and parsers ' me irl
|
||||
for them. Why did i do this? `.
|
||||
I have no idea! .-"-
|
||||
( |
|
||||
. .-' '.
|
||||
( (. )8:
|
||||
.' / (_ )
|
||||
_. :(. )8P `
|
||||
. ( `-' ( `. .
|
||||
. : ( .a8a)
|
||||
/_`( "a `a. )"'
|
||||
( (/ . ' )=='
|
||||
( ( ) .8" +
|
||||
(`'8a.( _( (
|
||||
..-. `8P ) ` ) +
|
||||
-' ( -ab: )
|
||||
' _ ` (8P"Ya
|
||||
_( ( )b -`. ) +
|
||||
( 8) ( _.aP" _a \( \ *
|
||||
+ )/ (8P (88 ) )
|
||||
(a:f " `"`
|
||||
|
||||
|
||||
The internal representation of formatted text is much like an s-expression.
|
||||
|
||||
They are specified as follows:
|
||||
[directive: this is the body of text to apply it to]
|
||||
|
||||
The colon and the space following are important! The first space is not part
|
||||
of the body, but any trailing spaces after it or at the end of the body are
|
||||
included in the output.
|
||||
|
||||
Escaping via backslash is supported. Nesting is supported as well, but escaping
|
||||
the delimiters is a bit tricky when nesting (both ends need to be escaped).
|
||||
See the following examples:
|
||||
|
||||
[bold: this here \] is totally valid, and so is [<-TOTALLY OK this]
|
||||
[bold: \[red: but both]<-CHOKE delimiters within a nest must be escaped.]
|
||||
|
||||
Directives are only parsed whenever the directive name is defined, and the
|
||||
colon/space follow it. Thus, including [brackets like this] in a post body
|
||||
will NOT require you to escape it! Even [brackets: like this] is safe, because
|
||||
brackets is not a defined formatting parameter. So, any amount of unescaped brackets
|
||||
may exist within the body unless they mimic a directive. To escape a valid directive,
|
||||
escaping only the opening is suffiecient: \[bold: like this]. The literal body of
|
||||
text outputted by that will be [bold: like this], with the backslash removed.
|
||||
|
||||
Just like the brackets themselves, backslashes may occur freely within bodies,
|
||||
they are only removed when they occur before a valid expression.
|
||||
"""
|
||||
|
||||
test = """
|
||||
This is a small paragraph
|
||||
thats divided between a
|
||||
few rows.
|
||||
|
||||
this opens a few linequotes.
|
||||
>this is a few
|
||||
>rows of
|
||||
>sequential line breaks
|
||||
and this is what follows right after
|
||||
"""
|
||||
|
||||
# from markdown import markdown
|
||||
# from html import escape
|
||||
import re
|
||||
|
||||
colors = [
|
||||
|
@ -28,16 +73,16 @@ markup = [
|
|||
"bold", "italic", "underline", "linequote", "quote", "rainbow"
|
||||
]
|
||||
|
||||
# PS: regex parsing is no longer used for these, preserving anyways
|
||||
# tokens being [red: this will be red] and [bold: this will be bold]
|
||||
# tokens = re.compile(r"\[(%s): (.+?)]" % "|".join(colors + markup), flags=re.DOTALL)
|
||||
# linequotes being chan-style greentext,
|
||||
# >like this
|
||||
# linequotes = re.compile("^(>.+)$", flags=re.MULTILINE)
|
||||
|
||||
# quotes being references to other post_ids, like >>34 or >>0 for OP
|
||||
quotes = re.compile(">>([0-9]+)")
|
||||
|
||||
# linequotes being chan-style greentext,
|
||||
# >like this
|
||||
linequotes = re.compile("^(>.+)$", flags=re.MULTILINE)
|
||||
|
||||
|
||||
def parse_segments(text, sanitize_linequotes=True):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue